splittet project to core and common functionalities

This commit is contained in:
2025-04-04 20:08:53 +02:00
parent 71d5d8303d
commit 6d0913fa0c
203 changed files with 780 additions and 726 deletions

33
common/build.gradle Normal file
View File

@ -0,0 +1,33 @@
plugins {
id 'java'
id("com.gradleup.shadow") version "8.3.5"
}
dependencies {
implementation project(':core')
compileOnly 'io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT'
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'
}
configurations {
shadowImplementation.extendsFrom implementation
}
shadowJar {
configurations = [project.configurations.shadowImplementation]
archiveClassifier.set('')
relocate 'org.apache.httpcomponents', 'eu.mhsl.lib.shadow.httpclient'
relocate 'com.sparkjava', 'eu.mhsl.lib.shadow.spark-core'
mergeServiceFiles()
}
jar {
enabled = false
}
build.dependsOn shadowJar

View File

@ -0,0 +1,47 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.antiSignEdit;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.SelectSetting;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
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;
if(setting.is(SignEditSetting.readOnly)) {
p.sendActionBar(Component.text("Das Bearbeiten von Schildern ist in deinen Einstellungen deaktiviert.", NamedTextColor.RED));
return true;
}
if(setting.is(SignEditSetting.editableWhenEmpty)) {
boolean hasText = sign.lines().stream()
.anyMatch(line -> !PlainTextComponentSerializer.plainText().serialize(line).isBlank());
if(hasText) {
p.sendActionBar(Component.text("Das Bearbeiten von Schildern, welch bereits beschrieben sind, ist bei dir deaktiviert.", NamedTextColor.RED));
return true;
}
}
return false;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new OnSignEditListener());
}
}

View File

@ -0,0 +1,15 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.antiSignEdit;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import io.papermc.paper.event.player.PlayerOpenSignEvent;
import org.bukkit.block.sign.SignSide;
import org.bukkit.event.EventHandler;
class OnSignEditListener extends ApplianceListener<AntiSignEdit> {
@EventHandler
public void onEdit(PlayerOpenSignEvent event) {
if(event.getCause().equals(PlayerOpenSignEvent.Cause.PLACE)) return;
SignSide signSide = event.getSign().getSide(event.getSide());
event.setCancelled(this.getAppliance().preventSignEdit(event.getPlayer(), signSide));
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.antiSignEdit;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import java.util.List;
import java.util.Locale;
public class SignEditSetting extends SelectSetting implements CategorizedSetting {
private static final String namespace = SignEditSetting.class.getSimpleName().toLowerCase(Locale.ROOT);
public static Options.Option editable = new Options.Option("Bearbeitbar", new NamespacedKey(namespace, "editable"));
public static Options.Option editableWhenEmpty = new Options.Option("Bearbeitbar wenn leer", new NamespacedKey(namespace, "emptyeditable"));
public static Options.Option readOnly = new Options.Option("Nicht bearbeitbar", new NamespacedKey(namespace, "readonly"));
public SignEditSetting() {
super(
Settings.Key.SignEdit,
new Options(List.of(editable, editableWhenEmpty, readOnly))
);
}
@Override
protected String title() {
return "Bearbeiten von Schildern";
}
@Override
protected String description() {
return "Bei einem Klick auf ein Schild, kann dieses Bearbeitet werden";
}
@Override
protected Material icon() {
return Material.OAK_SIGN;
}
@Override
protected Options.Option defaultValue() {
return editableWhenEmpty;
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
}

View File

@ -0,0 +1,53 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.autoShulker;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Material;
import org.bukkit.block.ShulkerBox;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.jetbrains.annotations.NotNull;
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();
if(itemStack.getType().equals(Material.SHULKER_BOX)) return false;
if(!offhandStack.getType().equals(Material.SHULKER_BOX)) return false;
BlockStateMeta blockStateMeta = (BlockStateMeta) offhandStack.getItemMeta();
ShulkerBox shulkerBox = (ShulkerBox) blockStateMeta.getBlockState();
HashMap<Integer, ItemStack> leftOver = shulkerBox.getInventory().addItem(itemStack);
if(leftOver.size() > 1)
throw new IllegalStateException("Multiple ItemStacks cannot be processed by AutoShulker!");
if(itemStack.equals(leftOver.get(0))) {
p.sendActionBar(Component.text("Die Shulkerbox ist voll!", NamedTextColor.RED));
return false;
}
if(leftOver.isEmpty()) {
item.remove();
}
blockStateMeta.setBlockState(shulkerBox);
offhandStack.setItemMeta(blockStateMeta);
return true;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new ItemPickupListener());
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.autoShulker;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import java.util.List;
import java.util.Locale;
public class AutoShulkerSetting extends SelectSetting implements CategorizedSetting {
private static final String namespace = AutoShulkerSetting.class.getSimpleName().toLowerCase(Locale.ROOT);
public static Options.Option disabled = new Options.Option("Deaktiviert", new NamespacedKey(namespace, "disabled"));
public static Options.Option notFromPlayers = new Options.Option("Keine manuell gedroppten Items", new NamespacedKey(namespace, "noplayerdrops"));
public static Options.Option enabled = new Options.Option("Alle", new NamespacedKey(namespace, "enabled"));
public AutoShulkerSetting() {
super(
Settings.Key.AutoShulker,
new Options(List.of(disabled, notFromPlayers, enabled))
);
}
@Override
protected String title() {
return "Shulker in offhand automatisch befüllen";
}
@Override
protected String description() {
return "Wenn eine Shulker in der zweiten Hand gehalten wird, werden alle aufgesammelten Items in dieser abgelegt";
}
@Override
protected Material icon() {
return Material.SHULKER_BOX;
}
@Override
protected Options.Option defaultValue() {
return disabled;
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.autoShulker;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityPickupItemEvent;
class ItemPickupListener extends ApplianceListener<AutoShulker> {
@EventHandler
public void onPickup(EntityPickupItemEvent event) {
if(event.getEntity() instanceof Player p) {
SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.AutoShulker, SelectSetting.Options.Option.class);
if(setting.is(AutoShulkerSetting.disabled)) return;
if(setting.is(AutoShulkerSetting.notFromPlayers) && event.getItem().getThrower() != null) return;
event.setCancelled(this.getAppliance().tryAutoShulker(p, event.getItem()));
}
}
}

View File

@ -0,0 +1,11 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.customAdvancements;
public class Advancements {
public static String searchTrouble = "search_trouble";
public static String fleischerchest = "fleischerchest";
public static String craftPixelblock = "craft_pixelblock";
public static String usePixelblock = "use_pixelblock";
public static String start = "start";
public static String winner = "winner";
public static String participateEvent = "participate_event";
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.customAdvancements;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class ApplyPendingAdvancementsListener extends ApplianceListener<CustomAdvancements> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().applyPendingAdvancements(event.getPlayer());
}
}

View File

@ -0,0 +1,66 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.customAdvancements;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.advancement.Advancement;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class CustomAdvancements extends Appliance {
record PendingAdvancement(UUID receiver, String advancement) {
}
private final List<PendingAdvancement> pendingAdvancements = new ArrayList<>();
public void grantAdvancement(String advancementName, UUID playerUUID) {
Player player = Bukkit.getPlayer(playerUUID);
if(player == null) {
this.addPendingAdvancement(playerUUID, advancementName);
return;
}
try {
NamespacedKey namespacedKey = Objects.requireNonNull(
NamespacedKey.fromString("craftattack_advancements:craftattack/" + advancementName),
String.format("NamespacedKey with '%s' is invalid!", advancementName)
);
Advancement advancement = Objects.requireNonNull(
Bukkit.getAdvancement(namespacedKey),
String.format("The advancement '%s' does not exist!", namespacedKey.asString())
);
player.getAdvancementProgress(advancement).awardCriteria("criteria");
} catch(Exception e) {
Main.logger().info("Advancement " + advancementName + " not found! (is Custom Advancements data pack loaded?)");
throw e;
}
}
public void applyPendingAdvancements(Player player) {
if(this.pendingAdvancements.isEmpty()) return;
List<PendingAdvancement> grantedAdvancements = this.pendingAdvancements.stream()
.filter(pendingAdvancement -> pendingAdvancement.receiver.equals(player.getUniqueId())).toList();
this.pendingAdvancements.removeAll(grantedAdvancements);
grantedAdvancements.forEach(pendingAdvancement -> this.grantAdvancement(pendingAdvancement.advancement(), player.getUniqueId()));
}
private void addPendingAdvancement(UUID receiver, String advancement) {
this.pendingAdvancements.add(new PendingAdvancement(receiver, advancement));
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(
new CustomAdvancementsListener(),
new ApplyPendingAdvancementsListener()
);
}
}

View File

@ -0,0 +1,46 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.customAdvancements;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.inventory.ItemStack;
class CustomAdvancementsListener extends ApplianceListener<CustomAdvancements> {
@EventHandler
public void onEntityDamageEntity(EntityDamageByEntityEvent event) {
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;
this.getAppliance().grantAdvancement(Advancements.searchTrouble, damager.getUniqueId());
}
@EventHandler
public void onCraftItem(CraftItemEvent event) {
ItemStack result = event.getInventory().getResult();
if(result == null) return;
if(!(event.getView().getPlayer() instanceof Player player)) return;
if(result.getType() == Material.RED_SHULKER_BOX) {
this.getAppliance().grantAdvancement(Advancements.fleischerchest, player.getUniqueId());
return;
}
if(result.getItemMeta().itemName().equals(Component.text("98fdf0ae-c3ab-4ef7-ae25-efd518d600de"))) {
this.getAppliance().grantAdvancement(Advancements.craftPixelblock, player.getUniqueId());
}
}
@EventHandler
public void onChangeWorld(PlayerChangedWorldEvent event) {
if(!event.getPlayer().getWorld().getName().startsWith("plugins/PixelBlocks/worlds")) return;
this.getAppliance().grantAdvancement(Advancements.usePixelblock, event.getPlayer().getUniqueId());
}
}

View File

@ -0,0 +1,51 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.doubleDoor;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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 = this.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.common.appliances.gameplay.doubleDoor;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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.common.appliances.gameplay.doubleDoor;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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;
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;
this.getAppliance().openNextDoor(clickedBlock);
}
}

View File

@ -0,0 +1,25 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.fleischerchest;
import eu.mhsl.craftattack.core.appliance.Appliance;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Fleischerchest extends Appliance {
public void renameItem(ItemStack item) {
ItemMeta meta = item.getItemMeta();
meta.displayName(Component.text("Fleischerchest").color(TextColor.color(235, 20, 28)));
item.setItemMeta(meta);
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(new FleischerchestCraftItemListener());
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.fleischerchest;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.inventory.PrepareItemCraftEvent;
import org.bukkit.inventory.ItemStack;
class FleischerchestCraftItemListener extends ApplianceListener<Fleischerchest> {
@EventHandler
public void onPrepareItemCraft(PrepareItemCraftEvent event) {
ItemStack result = event.getInventory().getResult();
if(result == null) return;
if(result.getType() != Material.RED_SHULKER_BOX) return;
this.getAppliance().renameItem(event.getInventory().getResult());
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.glowingBerries;
import eu.mhsl.craftattack.core.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,14 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.glowingBerries;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerItemConsumeEvent;
class OnBerryEaten extends ApplianceListener<GlowingBerries> {
@EventHandler
public void onEat(PlayerItemConsumeEvent event) {
if(event.getItem().getType().equals(Material.GLOW_BERRIES))
this.getAppliance().letPlayerGlow(event.getPlayer());
}
}

View File

@ -0,0 +1,57 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.hotbarRefill;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
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;
Inventory inventory = player.getInventory();
int itemSlot = inventory.first(item);
if(itemSlot > 8) return;
try {
int replacementSlot = inventory.all(item.getType()).entrySet().stream()
.filter(entry -> entry.getKey() > 8)
.findFirst()
.orElseThrow()
.getKey();
Bukkit.getScheduler().scheduleSyncDelayedTask(Main.instance(), () -> {
ItemStack firstItem = inventory.getItem(itemSlot);
ItemStack secondItem = inventory.getItem(replacementSlot);
inventory.setItem(itemSlot, secondItem);
inventory.setItem(replacementSlot, firstItem);
player.sendActionBar(Component.text("Die Hotbar wurde aufgefüllt", NamedTextColor.GREEN));
}, 1);
} catch(NoSuchElementException ignored) {
}
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new HotbarRefillListener());
}
}

View File

@ -0,0 +1,49 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.hotbarRefill;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerItemBreakEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
class HotbarRefillListener extends ApplianceListener<HotbarRefill> {
private HotbarRefillSetting.HotbarReplaceConfig getPlayerSetting(Player player) {
return Settings.instance().getSetting(
player,
Settings.Key.HotbarReplacer,
HotbarRefillSetting.HotbarReplaceConfig.class
);
}
@EventHandler
public void blockPlace(BlockPlaceEvent event) {
ItemStack stackInHand = event.getItemInHand();
if(stackInHand.getAmount() != 1) return;
if(stackInHand.getType().getMaxDurability() > 0) return;
if(stackInHand.getType().getMaxStackSize() > 0) return;
if(!this.getPlayerSetting(event.getPlayer()).onBlocks()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), stackInHand);
}
@EventHandler
public void onPlayerItemBreak(PlayerItemBreakEvent event) {
if(!this.getPlayerSetting(event.getPlayer()).onTools()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), event.getBrokenItem());
}
@EventHandler
public void onPlayerItemConsume(PlayerItemConsumeEvent event) {
if(List.of(Material.POTION, Material.HONEY_BOTTLE).contains(event.getItem().getType())) return;
if(!this.getPlayerSetting(event.getPlayer()).onConsumable()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), event.getItem());
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.hotbarRefill;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.MultiBoolSetting;
import org.bukkit.Material;
public class HotbarRefillSetting extends MultiBoolSetting<HotbarRefillSetting.HotbarReplaceConfig> implements CategorizedSetting {
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
public record HotbarReplaceConfig(
@DisplayName("Blöcke") boolean onBlocks,
@DisplayName("Werkzeuge") boolean onTools,
@DisplayName("Essen") boolean onConsumable
) {
}
public HotbarRefillSetting() {
super(Settings.Key.HotbarReplacer);
}
@Override
protected String title() {
return "Automatische Hotbar";
}
@Override
protected String description() {
return "Verschiebe Items automatisch von deinem Inventar in die Hotbar, wenn diese verbraucht werden";
}
@Override
protected Material icon() {
return Material.CHEST;
}
@Override
protected HotbarReplaceConfig defaultValue() {
return new HotbarReplaceConfig(false, false, false);
}
@Override
public Class<?> dataType() {
return HotbarReplaceConfig.class;
}
}

View File

@ -0,0 +1,65 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.knockDoor;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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)) {
this.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());
this.cancel();
return;
}
KnockDoor.this.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.common.appliances.gameplay.knockDoor;
import eu.mhsl.craftattack.core.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;
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;
this.getAppliance().knockAtDoor(event.getPlayer(), block);
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.knockDoor;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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,7 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.outlawed;
class OutlawChangeNotPermitted extends Exception {
public OutlawChangeNotPermitted(String message) {
super(message);
}
}

View File

@ -0,0 +1,134 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.outlawed;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName.DisplayName;
import eu.mhsl.craftattack.spawn.common.appliances.tooling.whitelist.Whitelist;
import eu.mhsl.craftattack.core.config.Configuration;
import eu.mhsl.craftattack.core.util.text.DisconnectInfo;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class Outlawed extends Appliance implements DisplayName.Prefixed {
public final int timeoutInMs = 1000 * 60 * 60 * 6;
private final Map<UUID, Long> timeouts = new HashMap<>();
public enum Status {
DISABLED,
VOLUNTARILY,
FORCED
}
private final Map<Player, Status> playerStatusMap = new WeakHashMap<>();
private final String voluntarilyEntry = "voluntarily";
public Outlawed() {
super("outlawed");
Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(),
() -> this.playerStatusMap.forEach((player, status) -> {
if(!player.isOnline()) return;
if(status != Status.FORCED) return;
try {
this.queryAppliance(Whitelist.class).fullIntegrityCheck(player);
} catch(DisconnectInfo.Throwable e) {
e.getDisconnectScreen().applyKick(player);
}
}),
20 * 60,
20 * 60 * 5
);
}
public void switchLawStatus(Player player) throws OutlawChangeNotPermitted {
if(this.getLawStatus(player).equals(Status.FORCED)) {
throw new OutlawChangeNotPermitted("Dein Vogelfreistatus wurde als Strafe auferlegt und kann daher nicht verändert werden.");
}
if(this.isTimeout(player)) {
throw new OutlawChangeNotPermitted("Du kannst deinen Vogelfreistatus nicht so schnell wechseln. Bitte warte einige Stunden bevor du umschaltest!");
}
this.setLawStatus(player, this.isOutlawed(player) ? Status.DISABLED : Status.VOLUNTARILY);
this.setTimeout(player);
}
public void updateForcedStatus(Player player, boolean forced) {
this.setLawStatus(player, forced ? Status.FORCED : this.getLawStatus(player) == Status.FORCED ? Status.DISABLED : this.getLawStatus(player));
}
public Status getLawStatus(Player player) {
return this.playerStatusMap.computeIfAbsent(player, p -> {
if(this.localConfig().getStringList(this.voluntarilyEntry).contains(p.getUniqueId().toString()))
return Status.VOLUNTARILY;
return Status.DISABLED;
});
}
private void setLawStatus(Player player, Status status) {
this.playerStatusMap.put(player, status);
this.queryAppliance(DisplayName.class).update(player);
List<String> newList = this.localConfig().getStringList(this.voluntarilyEntry);
if(status.equals(Status.VOLUNTARILY)) {
newList.add(player.getUniqueId().toString());
} else {
newList.remove(player.getUniqueId().toString());
}
this.localConfig().set(this.voluntarilyEntry, newList.stream().distinct().toList());
Configuration.saveChanges();
}
public boolean isOutlawed(Player player) {
return this.getLawStatus(player) != Status.DISABLED;
}
private boolean isTimeout(Player player) {
return this.timeouts.getOrDefault(player.getUniqueId(), 0L) > System.currentTimeMillis() - this.timeoutInMs;
}
private void setTimeout(Player player) {
this.timeouts.put(player.getUniqueId(), System.currentTimeMillis());
}
public Component getStatusDescription(Status status) {
return switch(status) {
case DISABLED -> Component.text("Vogelfreistatus inaktiv: ", NamedTextColor.GREEN)
.append(Component.text("Es gelten die Standard Regeln!", NamedTextColor.GOLD));
case VOLUNTARILY, FORCED -> Component.text("Vogelfreistatus aktiv: ", NamedTextColor.RED)
.append(Component.text("Du darfst von jedem angegriffen und getötet werden!", NamedTextColor.GOLD));
};
}
@Override
public Component getNamePrefix(Player player) {
if(this.isOutlawed(player)) {
return Component.text("[☠]", NamedTextColor.RED)
.hoverEvent(HoverEvent.showText(Component.text("Vogelfreie Spieler dürfen ohne Grund angegriffen werden!")));
}
return null;
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(new OutlawedCommand());
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(new OutlawedReminderListener());
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.outlawed;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> {
public OutlawedCommand() {
super("vogelfrei");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
try {
this.getAppliance().switchLawStatus(this.getPlayer());
sender.sendMessage(
this.getAppliance()
.getStatusDescription(this.getAppliance().getLawStatus(this.getPlayer()))
);
} catch(OutlawChangeNotPermitted e) {
sender.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
}
}
}

View File

@ -0,0 +1,14 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.outlawed;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class OutlawedReminderListener extends ApplianceListener<Outlawed> {
@EventHandler
public void onJoin(PlayerJoinEvent e) {
if(this.getAppliance().isOutlawed(e.getPlayer())) {
e.getPlayer().sendMessage(this.getAppliance().getStatusDescription(this.getAppliance().getLawStatus(e.getPlayer())));
}
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
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;
this.getAppliance().openFor(event.getPlayer());
}
}

View File

@ -0,0 +1,26 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
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> listeners() {
return List.of(new OnCraftingTableUseListener());
}
}

View File

@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.BoolSetting;
import org.bukkit.Material;
public class PortableCraftingSetting extends BoolSetting implements CategorizedSetting {
public PortableCraftingSetting() {
super(Settings.Key.EnablePortableCrafting);
}
@Override
protected String title() {
return "Portables Crafting";
}
@Override
protected String description() {
return "Erlaubt das öffnen einer Werkbank in der Hand, ohne sie plazieren zu müssen";
}
@Override
protected Material icon() {
return Material.CRAFTING_TABLE;
}
@Override
protected Boolean defaultValue() {
return true;
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
}

View File

@ -0,0 +1,22 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.snowballKnockback;
import eu.mhsl.craftattack.core.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,20 @@
package eu.mhsl.craftattack.spawn.common.appliances.gameplay.snowballKnockback;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.ProjectileHitEvent;
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();
this.getAppliance().dealSnowballKnockback(hitEntity, snowball);
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.craftattack.spawn.common.appliances.internal.debug;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.internal.debug.command.AppliancesCommand;
import eu.mhsl.craftattack.spawn.common.appliances.internal.debug.command.UserInfoCommand;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Debug extends Appliance {
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new UserInfoCommand(),
new AppliancesCommand()
);
}
}

View File

@ -0,0 +1,65 @@
package eu.mhsl.craftattack.spawn.common.appliances.internal.debug.command;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.internal.debug.Debug;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class AppliancesCommand extends ApplianceCommand<Debug> {
public AppliancesCommand() {
super("appliances");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
ComponentBuilder<TextComponent, TextComponent.Builder> componentBuilder = Component.text()
.append(Component.text(Main.instance().getAppliances().size()))
.append(Component.text(" appliances running:"))
.appendNewline();
Main.instance().getAppliances().forEach(appliance -> {
List<ApplianceCommand<?>> commands = appliance.getCommands();
List<Listener> listener = appliance.getListeners();
componentBuilder
.append(Component.text(appliance.getClass().getSimpleName(), NamedTextColor.GREEN)
.hoverEvent(HoverEvent.showText(Component.text(appliance.getClass().getName()))))
.append(Component.text(": ", NamedTextColor.DARK_GRAY))
.append(Component.text(commands.size() + " Commands", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(commands.stream()
.map(applianceCommand -> Component.text()
.append(Component.text(applianceCommand.commandName, NamedTextColor.DARK_GREEN))
.append(Component.text(": "))
.append(Component.text(applianceCommand.getClass().getName()))
.build())
.reduce(ComponentUtil::appendWithNewline)
.orElse(Component.text("No commands available")))))
.append(Component.text(", ", NamedTextColor.GRAY))
.append(Component.text(listener.size() + " Listener", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(listener.stream()
.map(eventHandler -> Component.text()
.append(Component.text(eventHandler.getClass().getSimpleName(), NamedTextColor.DARK_GREEN))
.append(Component.text(": "))
.append(Component.text(eventHandler.getClass().getName()))
.build())
.reduce(ComponentUtil::appendWithNewline)
.orElse(Component.text("No listeners available")))))
.appendNewline();
});
componentBuilder.append(Component.text(Main.instance().getClass().getName(), NamedTextColor.GRAY));
sender.sendMessage(componentBuilder.build());
}
}

View File

@ -0,0 +1,82 @@
package eu.mhsl.craftattack.spawn.common.appliances.internal.debug.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.internal.debug.Debug;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Stream;
public class UserInfoCommand extends ApplianceCommand<Debug> {
public UserInfoCommand() {
super("userInfo");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length != 1) {
sender.sendMessage(Component.text("Bitte gib einen Nutzernamen an.", NamedTextColor.RED));
return;
}
OfflinePlayer player = Bukkit.getOfflinePlayer(args[0]);
sender.sendMessage(
Component.text()
.appendNewline()
.append(Component.text("Informationen zu: ", NamedTextColor.GOLD))
.append(
Component
.text(Objects.requireNonNull(player.getName()), NamedTextColor.YELLOW)
.clickEvent(ClickEvent.copyToClipboard(Objects.requireNonNull(player.getName())))
)
.appendNewline()
.append(
Component
.text("UUID: " + player.getUniqueId(), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(player.getUniqueId().toString()))
)
.appendNewline()
.append(
Component
.text("Erster Besuch: " + this.formatUnixTimestamp(player.getFirstPlayed()), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(String.valueOf(player.getFirstPlayed())))
)
.appendNewline()
.append(
Component
.text("Letzter Besuch: " + this.formatUnixTimestamp(player.getLastSeen()), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(String.valueOf(player.getLastSeen())))
)
.appendNewline()
);
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length < 2) {
return Stream.concat(
Bukkit.getOnlinePlayers().stream().map(Player::getName),
Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName)
).toList();
}
return new ArrayList<>();
}
private String formatUnixTimestamp(long timestamp) {
DateFormat format = new SimpleDateFormat("E dd.MM.yyyy H:m:s");
return format.format(new Date(timestamp));
}
}

View File

@ -0,0 +1,22 @@
package eu.mhsl.craftattack.spawn.common.appliances.internal.titleClear;
import eu.mhsl.craftattack.core.appliance.Appliance;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class TitleClear extends Appliance {
public void clearTitle(Player player) {
player.clearTitle();
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(
new TitleClearListener()
);
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.common.appliances.internal.titleClear;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class TitleClearListener extends ApplianceListener<TitleClear> {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
this.getAppliance().clearTitle(event.getPlayer());
}
}

View File

@ -0,0 +1,14 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.adminMarker;
import eu.mhsl.craftattack.core.appliance.Appliance;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.entity.Player;
public class AdminMarker extends Appliance {
public TextColor getPlayerColor(Player player) {
if(player.hasPermission("chatcolor"))
return TextColor.color(Color.AQUA.asRGB()); // TODO read permission from config
return TextColor.color(Color.WHITE.asRGB());
}
}

View File

@ -0,0 +1,30 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.afkTag;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import io.papermc.paper.event.player.AsyncChatEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
class AfkResetListener extends ApplianceListener<AfkTag> {
@EventHandler
public void onMove(PlayerMoveEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
@EventHandler
public void onChat(AsyncChatEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
}

View File

@ -0,0 +1,74 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.afkTag;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName.DisplayName;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class AfkTag extends Appliance implements DisplayName.Prefixed {
private final HashMap<UUID, Long> afkTimings = new HashMap<>();
private static final int updateIntervalSeconds = 30;
private static final int afkWhenMillis = 3 * 60 * 1000;
@Override
public void onEnable() {
Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(),
this::checkAfkPlayers,
Ticks.TICKS_PER_SECOND,
Ticks.TICKS_PER_SECOND * updateIntervalSeconds
);
}
public void resetTiming(Player player) {
boolean wasAfk = this.isAfk(player);
this.afkTimings.put(player.getUniqueId(), System.currentTimeMillis());
if(wasAfk) this.updateAfkPrefix(player);
}
private void checkAfkPlayers() {
this.afkTimings.keySet().stream()
.map(Bukkit::getPlayer)
.filter(Objects::nonNull)
.filter(this::isAfk)
.forEach(this::updateAfkPrefix);
}
private boolean isAfk(Player player) {
if(player.isSleeping()) return false;
long lastTimeActive = this.afkTimings.getOrDefault(player.getUniqueId(), 0L);
long timeSinceLastActive = System.currentTimeMillis() - lastTimeActive;
return timeSinceLastActive >= afkWhenMillis;
}
private void updateAfkPrefix(Player player) {
Main.instance().getAppliance(DisplayName.class).update(player);
}
@Override
public @Nullable Component getNamePrefix(Player player) {
if(this.isAfk(player)) return Component.text("[ᵃᶠᵏ]", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(Component.text("Der Spieler ist AFK")));
return null;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new AfkResetListener());
}
}

View File

@ -0,0 +1,66 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMention;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.sound.Sound;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class ChatMention extends Appliance {
private static List<String> playerNames;
public String formatPlayer(String name) {
return "@" + name;
}
public List<String> getPlayerNames() {
return playerNames;
}
public void refreshPlayers() {
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> playerNames = Arrays.stream(Bukkit.getOfflinePlayers())
.map(OfflinePlayer::getName)
.filter(Objects::nonNull)
.toList()
);
}
public void notifyPlayers(List<String> playerNames) {
playerNames.stream()
.distinct()
.map(Bukkit::getPlayer)
.filter(Objects::nonNull)
.filter(player -> Settings.instance()
.getSetting(player, Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class)
.notifyOnMention()
)
.forEach(player -> player.playSound(
Sound.sound(
org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP,
Sound.Source.PLAYER,
1.0f,
1.0f
)
));
}
@Override
public void onEnable() {
Settings.instance().declareSetting(ChatMentionSetting.class);
this.refreshPlayers();
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new ChatMentionListener());
}
}

View File

@ -0,0 +1,56 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMention;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.ArrayList;
import java.util.List;
class ChatMentionListener extends ApplianceListener<ChatMention> {
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void coloringEvent(AsyncChatDecorateEvent event) {
String message = PlainTextComponentSerializer.plainText().serialize(event.result());
List<String> words = List.of(message.split(" "));
List<String> mentioned = new ArrayList<>();
ChatMentionSetting.ChatMentionConfig config = Settings.instance()
.getSetting(event.player(), Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class);
ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
Component result = words.stream()
.map(word -> {
String wordWithoutAnnotation = word.replace("@", "");
boolean isPlayer = this.getAppliance().getPlayerNames().contains(wordWithoutAnnotation);
if(isPlayer && config.applyMentions()) {
mentioned.add(wordWithoutAnnotation);
Component mention = Component.text(
this.getAppliance().formatPlayer(wordWithoutAnnotation),
NamedTextColor.GOLD
);
return chatMessages.addReportActions(mention, wordWithoutAnnotation);
} else {
return Component.text(word);
}
})
.reduce(ComponentUtil::appendWithSpace)
.orElseThrow();
this.getAppliance().notifyPlayers(mentioned);
event.result(result);
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().refreshPlayers();
}
}

View File

@ -0,0 +1,49 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMention;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.MultiBoolSetting;
import org.bukkit.Material;
public class ChatMentionSetting extends MultiBoolSetting<ChatMentionSetting.ChatMentionConfig> implements CategorizedSetting {
@Override
public SettingCategory category() {
return SettingCategory.Visuals;
}
public record ChatMentionConfig(
@DisplayName("Spielernamen hervorheben") boolean applyMentions,
@DisplayName("Benachrichtigungston") boolean notifyOnMention
) {
}
public ChatMentionSetting() {
super(Settings.Key.ChatMentions);
}
@Override
protected String title() {
return "Erwähnungen im Chat";
}
@Override
protected String description() {
return "Erwähnungen werden automatisch im Chat angewandt und der Empfänger erhält einen Signalton";
}
@Override
protected Material icon() {
return Material.FEATHER;
}
@Override
protected ChatMentionConfig defaultValue() {
return new ChatMentionConfig(true, true);
}
@Override
public Class<?> dataType() {
return ChatMentionConfig.class;
}
}

View File

@ -0,0 +1,36 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.text.Component;
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.bukkit.event.Listener;
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 this.addReportActions(player.displayName(), player.getName());
}
public Component addReportActions(Component message, String username) {
return message
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD)))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, String.format("/report %s ", username)));
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(new ChatMessagesListener());
}
}

View File

@ -0,0 +1,70 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.core.util.IteratorUtil;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Optional;
class ChatMessagesListener extends ApplianceListener<ChatMessages> {
@EventHandler
public void onPlayerChatEvent(AsyncChatEvent event) {
event.renderer(
(source, sourceDisplayName, message, viewer) ->
Component.text("")
.append(this.getAppliance().getReportablePlayerName(source))
.append(Component.text(" > ").color(TextColor.color(Color.GRAY.asRGB())))
.append(message).color(TextColor.color(Color.SILVER.asRGB()))
);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerJoin(PlayerJoinEvent event) {
event.joinMessage(null);
IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage(
Component
.text(">>> ").color(NamedTextColor.GREEN)
.append(this.getAppliance().getReportablePlayerName(event.getPlayer()))
);
});
}
@EventHandler
public void onPlayerLeave(PlayerQuitEvent event) {
event.quitMessage(null);
IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage(
Component
.text("<<< ").color(NamedTextColor.RED)
.append(this.getAppliance().getReportablePlayerName(event.getPlayer()))
);
});
}
@EventHandler
public void onDeath(PlayerDeathEvent event) {
event.deathMessage(
Component
.text("")
.append(
Optional
.ofNullable(event.deathMessage())
.orElse(Component.text(event.getPlayer().getName()))
)
.color(TextColor.color(Color.SILVER.asRGB()))
);
}
}

View File

@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.BoolSetting;
import org.bukkit.Material;
public class ShowJoinAndLeaveMessagesSetting extends BoolSetting implements CategorizedSetting {
public ShowJoinAndLeaveMessagesSetting() {
super(Settings.Key.ShowJoinAndLeaveMessages);
}
@Override
protected String title() {
return "Join & Leave Nachrichten anzeigen";
}
@Override
protected String description() {
return "Zeige allgemeine Beitritts und Verlassensmeldungen im Chat";
}
@Override
protected Material icon() {
return Material.PLAYER_HEAD;
}
@Override
protected Boolean defaultValue() {
return true;
}
@Override
public SettingCategory category() {
return SettingCategory.Visuals;
}
}

View File

@ -0,0 +1,75 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.common.appliances.gameplay.outlawed.Outlawed;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.afkTag.AfkTag;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.sleepTag.SleepTag;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.yearRank.YearRank;
import eu.mhsl.craftattack.core.util.server.Floodgate;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.logging.Level;
public class DisplayName extends Appliance {
public interface Prefixed {
@Nullable
Component getNamePrefix(Player player);
}
public void update(Player player) {
TextColor playerColor = this.queryAppliance(AdminMarker.class).getPlayerColor(player);
List<Supplier<Prefixed>> prefixes = List.of(
() -> this.queryAppliance(Outlawed.class),
() -> this.queryAppliance(YearRank.class),
() -> this.queryAppliance(AfkTag.class),
() -> this.queryAppliance(SleepTag.class)
);
ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
prefixes.stream()
.map(prefixed -> prefixed.get().getNamePrefix(player))
.filter(Objects::nonNull)
.forEach(prefix -> playerName
.append(prefix)
.append(ComponentUtil.clearedSpace())
);
if(Floodgate.isBedrock(player)) {
playerName
.append(
Component.text("\uD83C\uDFAE", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(Component.text(
String.format("%s spielt die Minecraft: Bedrock Edition", player.getName())
)))
)
.append(ComponentUtil.clearedSpace());
}
playerName.append(Component.text(player.getName(), playerColor));
this.setGlobal(player, playerName.build());
}
private void setGlobal(Player player, Component component) {
try {
player.customName(component);
player.displayName(component);
player.playerListName(component);
} catch(Exception e) {
Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage);
}
}
}

View File

@ -0,0 +1,13 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.displayName;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerJoinEvent;
class DisplayNameUpdateListener extends ApplianceListener<DisplayName> {
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().update(event.getPlayer());
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class ApplyPendingRewardsListener extends ApplianceListener<Event> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().applyPendingRewards(event.getPlayer());
}
}

View File

@ -0,0 +1,230 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.api.client.ReqResp;
import eu.mhsl.craftattack.core.api.client.repositories.EventRepository;
import eu.mhsl.craftattack.core.api.server.HttpServer;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.gameplay.customAdvancements.Advancements;
import eu.mhsl.craftattack.spawn.common.appliances.gameplay.customAdvancements.CustomAdvancements;
import eu.mhsl.craftattack.core.util.IteratorUtil;
import eu.mhsl.craftattack.core.util.api.HttpStatus;
import eu.mhsl.craftattack.core.util.entity.DisplayVillager;
import eu.mhsl.craftattack.core.util.listener.DismissInventoryOpenFromHolder;
import eu.mhsl.craftattack.core.util.listener.PlayerInteractAtEntityEventListener;
import eu.mhsl.craftattack.core.util.server.PluginMessage;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import eu.mhsl.craftattack.core.util.text.Countdown;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.command.*;
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.entity.Villager;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class Event extends Appliance {
enum AdvertisementStatus {
BEFORE,
ADVERTISED,
DONE
}
Countdown advertiseCountdown = new Countdown(
120,
announcementData -> Component.text()
.append(ComponentUtil.createRainbowText("Event", 30))
.append(Component.text(" Start in ", NamedTextColor.GOLD))
.append(Component.text(announcementData.count(), NamedTextColor.AQUA))
.append(Component.text(" " + announcementData.unit() + "!", NamedTextColor.GOLD))
.build(),
component -> IteratorUtil.onlinePlayers(player -> player.sendMessage(component)),
() -> this.advertiseStatus = AdvertisementStatus.DONE
);
public DisplayVillager.ConfigBound villager;
private boolean isOpen = false;
private AdvertisementStatus advertiseStatus = AdvertisementStatus.BEFORE;
private UUID roomId;
private final List<Reward> pendingRewards = new ArrayList<>();
record RewardConfiguration(String memorialMaterial, String memorialTitle, String memorialLore, List<UUID> memorials,
String material, Map<UUID, Integer> rewards) {
}
record Reward(UUID playerUuid, ItemStack itemStack) {
}
public Event() {
super("event");
}
@Override
public void onEnable() {
this.villager = new DisplayVillager.ConfigBound(
this.localConfig(),
villager -> {
villager.customName(Component.text("Events", NamedTextColor.GOLD));
villager.setProfession(Villager.Profession.LIBRARIAN);
villager.setVillagerType(Villager.Type.SNOW);
}
);
this.isOpen = this.localConfig().getBoolean("enabled", false);
if(this.isOpen) this.roomId = UUID.fromString(this.localConfig().getString("roomId", ""));
}
public void openEvent() {
if(this.isOpen) throw new ApplianceCommand.Error("Es läuft derzeit bereits ein Event!");
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.CreatedRoom> sessionResponse = this.queryRepository(EventRepository.class).createSession();
if(sessionResponse.status() != HttpStatus.OK)
throw new ApplianceCommand.Error("Event-Server meldet Fehler: " + sessionResponse.status());
this.isOpen = true;
this.roomId = sessionResponse.data().uuid();
});
}
public void joinEvent(Player p) {
if(!this.isOpen) {
p.sendMessage(Component.text("Zurzeit ist kein Event geöffnet.", NamedTextColor.RED));
return;
}
if(!p.hasPermission("admin") && this.advertiseStatus == AdvertisementStatus.BEFORE) {
p.sendMessage(Component.text("Die Event befinden sich noch in der Vorbereitung.", NamedTextColor.RED));
return;
}
if(!p.hasPermission("admin") && this.advertiseStatus == AdvertisementStatus.DONE) {
p.sendMessage(Component.text("Die Events laufen bereits. Ein nachträgliches Beitreten ist nicht möglich.", NamedTextColor.RED));
return;
}
Main.instance().getLogger().info("Verbinde mit eventserver: " + p.getName());
p.sendMessage(Component.text("Authentifiziere...", NamedTextColor.GREEN));
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.QueueRoom.Response> queueResponse = this.queryRepository(EventRepository.class)
.queueRoom(new EventRepository.QueueRoom(p.getUniqueId(), this.roomId));
if(queueResponse.status() != HttpStatus.OK || queueResponse.data().error() != null) {
p.sendMessage(Component.text("Fehler beim Betreten: " + queueResponse.data().error(), NamedTextColor.RED));
return;
}
p.sendMessage(Component.text("Betrete...", NamedTextColor.GREEN));
PluginMessage.connect(p, this.localConfig().getString("connect-server-name"));
});
}
public void endEvent() {
if(!this.isOpen) throw new ApplianceCommand.Error("Es läuft derzeit kein Event!");
this.isOpen = false;
}
private void rewardPlayers(RewardConfiguration rewardConfiguration) {
rewardConfiguration.rewards.forEach((uuid, amount) -> {
Reward reward = new Reward(uuid, new ItemStack(Objects.requireNonNull(Material.matchMaterial(rewardConfiguration.material)), amount));
if(Bukkit.getPlayer(uuid) == null) {
this.pendingRewards.add(reward);
return;
}
this.giveReward(reward);
});
rewardConfiguration.memorials.forEach(uuid -> {
ItemStack memorialItem = new ItemStack(Objects.requireNonNull(Material.matchMaterial(rewardConfiguration.memorialMaterial)));
ItemMeta meta = memorialItem.getItemMeta();
meta.displayName(Component.text(rewardConfiguration.memorialTitle, NamedTextColor.GOLD));
meta.lore(List.of(Component.text(rewardConfiguration.memorialLore, NamedTextColor.AQUA)));
memorialItem.setItemMeta(meta);
Reward memorial = new Reward(uuid, memorialItem);
Main.instance().getAppliance(CustomAdvancements.class).grantAdvancement(Advancements.participateEvent, uuid);
if(Bukkit.getPlayer(uuid) == null) {
this.pendingRewards.add(memorial);
return;
}
this.giveReward(memorial);
});
rewardConfiguration.rewards.keySet().stream()
.max(Comparator.comparing(rewardConfiguration.rewards::get))
.ifPresent(uuid -> Main.instance().getAppliance(CustomAdvancements.class)
.grantAdvancement(Advancements.winner, uuid));
}
private void giveReward(Reward reward) {
Player player = Bukkit.getPlayer(reward.playerUuid);
if(player == null) throw new RuntimeException("Cannot reward offline playerUuid!");
Map<Integer, ItemStack> remaining = player.getInventory().addItem(reward.itemStack);
Bukkit.getScheduler().runTask(
Main.instance(),
() -> remaining.values().forEach(remainingStack -> player.getWorld().dropItem(player.getLocation(), remainingStack))
);
}
public void applyPendingRewards(Player player) {
if(this.pendingRewards.isEmpty()) return;
List<Reward> givenRewards = this.pendingRewards.stream().filter(reward -> reward.playerUuid.equals(player.getUniqueId())).toList();
this.pendingRewards.removeAll(givenRewards);
givenRewards.forEach(this::giveReward);
}
public void advertise() {
this.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()
));
this.advertiseCountdown.start();
}
@Override
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
apiBuilder.post("reward", RewardConfiguration.class, (rewardConfiguration, request) -> {
this.rewardPlayers(rewardConfiguration);
return HttpServer.nothing;
});
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new EventCommand(),
new MoveEventVillagerCommand(),
new EventOpenSessionCommand(),
new EventEndSessionCommand(),
new EventAdvertiseCommand()
);
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(
new ApplyPendingRewardsListener(),
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> this.joinEvent(playerInteractAtEntityEvent.getPlayer())),
new DismissInventoryOpenFromHolder(this.villager.getUniqueId())
);
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class EventAdvertiseCommand extends ApplianceCommand<Event> {
public EventAdvertiseCommand() {
super("eventAdvertise");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
this.getAppliance().advertise();
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class EventCommand extends ApplianceCommand.PlayerChecked<Event> {
public EventCommand() {
super("event");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
this.getAppliance().joinEvent(this.getPlayer());
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class EventEndSessionCommand extends ApplianceCommand<Event> {
public EventEndSessionCommand() {
super("eventEndSession");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
this.getAppliance().endEvent();
}
}

View File

@ -0,0 +1,21 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.Event;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class EventOpenSessionCommand extends ApplianceCommand<Event> {
public EventOpenSessionCommand() {
super("eventOpenSession");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
this.getAppliance().openEvent();
sender.sendMessage(Component.text("Event-Server gestartet!", NamedTextColor.GREEN));
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class MoveEventVillagerCommand extends ApplianceCommand.PlayerChecked<Event> {
public MoveEventVillagerCommand() {
super("moveEventVillager");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
this.getAppliance().villager.updateLocation(this.getPlayer().getLocation());
}
}

View File

@ -0,0 +1,77 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.feedback;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.api.client.ReqResp;
import eu.mhsl.craftattack.core.api.client.repositories.FeedbackRepository;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.core.util.api.HttpStatus;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
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.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class Feedback extends Appliance {
public Feedback() {
super("feedback");
}
public void requestFeedback(String eventName, List<Player> receivers, @Nullable String question) {
ReqResp<Map<UUID, String>> response = this.queryRepository(FeedbackRepository.class).createFeedbackUrls(
new FeedbackRepository.Request(eventName, receivers.stream().map(Entity::getUniqueId).toList())
);
System.out.println(response.toString());
System.out.println(response.status());
if(response.status() != HttpStatus.CREATED) throw new RuntimeException();
Component border = Component.text("-".repeat(40), NamedTextColor.GRAY);
receivers.forEach(player -> {
String feedbackUrl = response.data().get(player.getUniqueId());
if(feedbackUrl == null) {
Main.logger().warning(String.format("FeedbackUrl not found for player '%s' from backend!", player.getUniqueId()));
return;
}
ComponentBuilder<TextComponent, TextComponent.Builder> message = Component.text()
.append(border)
.appendNewline();
if(question != null) {
message
.append(Component.text(question, NamedTextColor.GREEN))
.appendNewline()
.appendNewline();
}
message
.append(Component.text("Klicke hier und gib uns Feedback, damit wir dein Spielerlebnis verbessern können!", NamedTextColor.DARK_GREEN)
.clickEvent(ClickEvent.openUrl(feedbackUrl)))
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um Feedback zu geben.")))
.appendNewline()
.append(border);
player.sendMessage(message.build());
});
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new FeedbackCommand(),
new RequestFeedbackCommand()
);
}
}

View File

@ -0,0 +1,30 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.feedback;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
class FeedbackCommand extends ApplianceCommand.PlayerChecked<Feedback> {
public FeedbackCommand() {
super("feedback");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
sender.sendMessage(ComponentUtil.pleaseWait());
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.getAppliance().requestFeedback(
"self-issued-ingame",
List.of(this.getPlayer()),
null
)
);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.feedback;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
class RequestFeedbackCommand extends ApplianceCommand<Feedback> {
public RequestFeedbackCommand() {
super("requestFeedback");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.getAppliance().requestFeedback(
"admin-issued-ingame",
new ArrayList<>(Bukkit.getOnlinePlayers()), String.join(" ", args)
)
);
}
}

View File

@ -0,0 +1,28 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command.DiscordCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command.HelpCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command.SpawnCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command.TeamspeakCommand;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Help extends Appliance {
public Help() {
super("help");
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new HelpCommand(),
new SpawnCommand(),
new TeamspeakCommand(),
new DiscordCommand()
);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class DiscordCommand extends ApplianceCommand<Help> {
public DiscordCommand() {
super("discord");
}
private final static String discordLink = "https://discord.gg/TXxspGVanq";
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
sender.sendMessage(
Component.text("Offizieller Discord Server: ", NamedTextColor.GOLD)
.append(Component.text(discordLink, NamedTextColor.AQUA))
.clickEvent(ClickEvent.openUrl(discordLink))
);
}
}

View File

@ -0,0 +1,25 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class HelpCommand extends ApplianceCommand<Help> {
public HelpCommand() {
super("help");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage(
Component.text("Willkommen auf Craftattack!", NamedTextColor.GOLD)
.appendNewline()
.append(Component.text("Wenn du hilfe benötigst kannst du dich jederzeit an einen Admin wenden." +
" Weitere Informationen zu Funktionen und Befehlen erhältst du zudem im Turm am Spawn.", NamedTextColor.GRAY))
);
}
}

View File

@ -0,0 +1,26 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class SpawnCommand extends ApplianceCommand<Help> {
private static final String spawnKey = "spawn";
public SpawnCommand() {
super("spawn");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!this.getAppliance().localConfig().isString(spawnKey))
throw new ApplianceCommand.Error("Es wurde kein Spawnbereich hinterlegt!");
sender.sendMessage(Component.text(Objects.requireNonNull(this.getAppliance().localConfig().getString(spawnKey)), NamedTextColor.GOLD));
}
}

View File

@ -0,0 +1,34 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class TeamspeakCommand extends ApplianceCommand<Help> {
private static final String teamspeakKey = "teamspeak";
public TeamspeakCommand() {
super("teamspeak");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!this.getAppliance().localConfig().isString(teamspeakKey))
throw new ApplianceCommand.Error("Es wurde kein Teamspeak hinterlegt!");
sender.sendMessage(
Component.text()
.append(Component.text("Joine unserem Teamspeak: ", NamedTextColor.GOLD))
.append(this.getTeamspeakIp(this.getAppliance().localConfig().getString(teamspeakKey)))
);
}
private Component getTeamspeakIp(String ip) {
return Component.text()
.append(Component.text(ip, NamedTextColor.AQUA))
.build();
}
}

View File

@ -0,0 +1,69 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.core.Main;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public abstract class Bar {
private BossBar bossBar;
private final BukkitTask updateTask;
public Bar() {
long refreshRateInTicks = this.refresh().get(ChronoUnit.SECONDS) * Ticks.TICKS_PER_SECOND;
this.updateTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(),
this::update,
refreshRateInTicks,
refreshRateInTicks
);
}
public BossBar getBossBar() {
if(this.bossBar == null) this.bossBar = this.createBar();
return this.bossBar;
}
private BossBar createBar() {
return BossBar.bossBar(
this.title(),
this.correctedProgress(),
this.color(),
this.overlay()
);
}
private void update() {
if(this.bossBar == null) return;
this.beforeRefresh();
this.bossBar.name(this.title());
this.bossBar.progress(this.correctedProgress());
this.bossBar.color(this.color());
this.bossBar.overlay(this.overlay());
}
public void stopUpdate() {
this.updateTask.cancel();
}
private float correctedProgress() {
return Math.clamp(this.progress(), 0, 1);
}
protected void beforeRefresh() {
}
protected abstract Duration refresh();
protected abstract String name();
protected abstract Component title();
protected abstract float progress();
protected abstract BossBar.Color color();
protected abstract BossBar.Overlay overlay();
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.core.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;
class InfoBarCommand extends ApplianceCommand.PlayerChecked<InfoBars> {
public InfoBarCommand() {
super("infobar");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
if(args.length == 0) throw new Error("<show|hide|hideall> [bar name]");
switch(args[0]) {
case "hideAll" -> this.getAppliance().hideAll(this.getPlayer());
case "show" -> this.getAppliance().show(this.getPlayer(), args[1]);
case "hide" -> this.getAppliance().hide(this.getPlayer(), args[1]);
default -> throw new Error("Erlaubte Optionen sind 'show', 'hide', 'hideAll'!");
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length == 1) return List.of("show", "hide", "hideAll");
return this.getAppliance().getInfoBars().stream().map(Bar::name).toList();
}
}

View File

@ -0,0 +1,92 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.MsptBar;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.PlayerCounterBar;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars.TpsBar;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class InfoBars extends Appliance {
private final NamespacedKey infoBarKey = new NamespacedKey(Main.instance(), "infobars");
private final List<Bar> infoBars = List.of(
new TpsBar(),
new MsptBar(),
new PlayerCounterBar()
);
public void showAll(Player player) {
this.getStoredBars(player).forEach(bar -> this.show(player, bar));
}
public void hideAll(Player player) {
this.getStoredBars(player).forEach(bar -> this.hide(player, bar));
this.setStoredBars(player, List.of());
}
public void show(Player player, String bar) {
this.validateBarName(bar);
List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
existingBars.add(bar);
player.showBossBar(this.getBarByName(bar).getBossBar());
this.setStoredBars(player, existingBars);
}
public void hide(Player player, String bar) {
this.validateBarName(bar);
List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
existingBars.remove(bar);
player.hideBossBar(this.getBarByName(bar).getBossBar());
this.setStoredBars(player, existingBars);
}
private List<String> getStoredBars(Player player) {
PersistentDataContainer container = player.getPersistentDataContainer();
if(!container.has(this.infoBarKey)) return List.of();
return container.get(this.infoBarKey, PersistentDataType.LIST.strings());
}
private void setStoredBars(Player player, List<String> bars) {
player.getPersistentDataContainer().set(this.infoBarKey, PersistentDataType.LIST.strings(), bars);
}
private Bar getBarByName(String name) {
return this.infoBars.stream()
.filter(bar -> bar.name().equalsIgnoreCase(name))
.findFirst()
.orElse(null);
}
private void validateBarName(String name) {
if(this.getBarByName(name) == null)
throw new ApplianceCommand.Error(String.format("Ungültiger infobar name '%s'", name));
}
public List<Bar> getInfoBars() {
return this.infoBars;
}
@Override
public void onDisable() {
this.infoBars.forEach(Bar::stopUpdate);
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new ShowPreviousBarsListener());
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(new InfoBarCommand());
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class ShowPreviousBarsListener extends ApplianceListener<InfoBars> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
// this.getAppliance().showAll(event.getPlayer());
}
}

View File

@ -0,0 +1,57 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.Bar;
import eu.mhsl.craftattack.core.util.statistics.ServerMonitor;
import eu.mhsl.craftattack.core.util.text.ColorUtil;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.time.Duration;
public class MsptBar extends Bar {
@Override
protected Duration refresh() {
return Duration.ofSeconds(3);
}
@Override
protected String name() {
return "mspt";
}
@Override
protected Component title() {
return Component.text()
.append(Component.text("M"))
.append(Component.text("illi", NamedTextColor.GRAY))
.append(Component.text("S"))
.append(Component.text("econds ", NamedTextColor.GRAY))
.append(Component.text("P"))
.append(Component.text("er ", NamedTextColor.GRAY))
.append(Component.text("T"))
.append(Component.text("ick", NamedTextColor.GRAY))
.append(Component.text(": "))
.append(Component.text(String.format("%.2f", this.currentMSPT()), ColorUtil.msptColor(this.currentMSPT())))
.build();
}
@Override
protected float progress() {
return this.currentMSPT() / 50f;
}
@Override
protected BossBar.Color color() {
return BossBar.Color.BLUE;
}
@Override
protected BossBar.Overlay overlay() {
return BossBar.Overlay.PROGRESS;
}
private float currentMSPT() {
return ServerMonitor.getServerMSPT();
}
}

View File

@ -0,0 +1,56 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.Bar;
import eu.mhsl.craftattack.spawn.common.appliances.tooling.playerlimit.PlayerLimit;
import eu.mhsl.craftattack.core.util.text.ColorUtil;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Bukkit;
import java.time.Duration;
public class PlayerCounterBar extends Bar {
@Override
protected Duration refresh() {
return Duration.ofSeconds(3);
}
@Override
protected String name() {
return "playerCounter";
}
@Override
protected Component title() {
TextColor color = ColorUtil.mapGreenToRed(this.getCurrentPlayerCount(), 0, this.getMaxPlayerCount(), true);
return Component.text()
.append(Component.text("Spieler online: "))
.append(Component.text(this.getCurrentPlayerCount(), color))
.build();
}
@Override
protected float progress() {
return (float) this.getCurrentPlayerCount() / this.getMaxPlayerCount();
}
@Override
protected BossBar.Color color() {
return BossBar.Color.BLUE;
}
@Override
protected BossBar.Overlay overlay() {
return BossBar.Overlay.PROGRESS;
}
private int getCurrentPlayerCount() {
return Bukkit.getOnlinePlayers().size();
}
private int getMaxPlayerCount() {
return Main.instance().getAppliance(PlayerLimit.class).getLimit();
}
}

View File

@ -0,0 +1,55 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.bars;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.infoBars.Bar;
import eu.mhsl.craftattack.core.util.text.ColorUtil;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import java.time.Duration;
public class TpsBar extends Bar {
@Override
protected Duration refresh() {
return Duration.ofSeconds(3);
}
@Override
protected String name() {
return "tps";
}
@Override
protected Component title() {
return Component.text()
.append(Component.text("T"))
.append(Component.text("icks ", NamedTextColor.GRAY))
.append(Component.text("P"))
.append(Component.text("er ", NamedTextColor.GRAY))
.append(Component.text("S"))
.append(Component.text("econds", NamedTextColor.GRAY))
.append(Component.text(": "))
.append(Component.text(String.format("%.2f", this.currentTps()), ColorUtil.tpsColor(this.currentTps())))
.build();
}
@Override
protected float progress() {
return this.currentTps() / 20;
}
@Override
protected BossBar.Color color() {
return BossBar.Color.BLUE;
}
@Override
protected BossBar.Overlay overlay() {
return BossBar.Overlay.NOTCHED_20;
}
private float currentTps() {
return (float) Bukkit.getTPS()[0];
}
}

View File

@ -0,0 +1,70 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.optionLinks;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.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.common.appliances.metaGameplay.optionLinks;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class UpdateLinksListener extends ApplianceListener<OptionLinks> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().setServerLinks(event.getPlayer());
}
}

View File

@ -0,0 +1,19 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
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 {
this.getAppliance().openPackInventory(this.getPlayer());
}
}

View File

@ -0,0 +1,99 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import eu.mhsl.craftattack.core.appliance.CachedApplianceSupplier;
import eu.mhsl.craftattack.core.util.inventory.HeadBuilder;
import eu.mhsl.craftattack.core.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(this.icon);
ItemMeta meta = stack.getItemMeta();
meta.itemName(Component.text(this.id.toString()));
meta.displayName(Component.text(this.name(), NamedTextColor.GOLD));
List<Component> lore = new ArrayList<>();
lore.add(Component.text("Autor: ", NamedTextColor.DARK_GRAY).append(Component.text(this.author)));
lore.add(Component.text(" "));
lore.addAll(
ComponentUtil.lineBreak(this.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(this.id);
} catch(IllegalArgumentException ignored) {
return false;
}
}
}
public record PackList(List<Pack> packs) {
public Optional<Pack> findFromItem(ItemStack itemStack) {
return this.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 = this.gson.fromJson(data, SerializedPackList.class);
var availablePackMap = this.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 this.gson.toJson(new SerializedPackList(this.packList.packs().stream().map(Pack::id).toList()));
}
public List<Pack> getPackList() {
return this.packList.packs();
}
}

View File

@ -0,0 +1,171 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.CachedApplianceSupplier;
import eu.mhsl.craftattack.core.util.IteratorUtil;
import eu.mhsl.craftattack.core.util.inventory.ItemBuilder;
import eu.mhsl.craftattack.core.util.inventory.PlaceholderItems;
import eu.mhsl.craftattack.core.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 this.inventory;
}
public void handleClick(@Nullable ItemStack clickedItem, int slot, ClickType clickType) {
if(clickedItem == null) return;
if(clickedItem.equals(this.reset)) this.reset();
if(clickedItem.equals(this.save)) this.apply();
if(slot >= 9 && slot < 9 * 4) {
this.getAppliance().availablePacks.findFromItem(clickedItem)
.ifPresent(this::toggle);
}
if(slot >= 9 * 5) {
this.getAppliance().availablePacks.findFromItem(clickedItem)
.ifPresent(pack -> {
switch(clickType) {
case RIGHT -> this.move(pack, true);
case LEFT -> this.move(pack, false);
case MIDDLE -> this.toggle(pack);
}
});
}
}
private void move(PackConfiguration.Pack pack, boolean moveToRight) {
List<PackConfiguration.Pack> packs = this.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(this.inventoryOwner).click();
this.draw();
}
private void toggle(PackConfiguration.Pack pack) {
if(this.packConfiguration.getPackList().contains(pack)) {
this.packConfiguration.getPackList().remove(pack);
} else {
this.packConfiguration.getPackList().add(pack);
}
InteractSounds.of(this.inventoryOwner).click();
this.draw();
}
private void reset() {
this.packConfiguration.getPackList().clear();
InteractSounds.of(this.inventoryOwner).delete();
this.draw();
}
private void apply() {
this.inventoryOwner.closeInventory();
InteractSounds.of(this.inventoryOwner).success();
Bukkit.getScheduler().runTask(Main.instance(), () -> this.getAppliance().setPack(this.inventoryOwner, this.packConfiguration));
}
private void draw() {
this.inventory.clear();
this.inventory.setItem(0, this.packConfiguration.getPackList().isEmpty() ? PlaceholderItems.grayStainedGlassPane : this.reset);
IteratorUtil.times(3, () -> this.inventory.addItem(PlaceholderItems.grayStainedGlassPane));
this.inventory.setItem(4, this.info);
IteratorUtil.times(3, () -> this.inventory.addItem(PlaceholderItems.grayStainedGlassPane));
this.inventory.setItem(8, this.save);
IteratorUtil.iterateListInGlobal(
9,
this.getAppliance().availablePacks.packs().stream()
.filter(pack -> !this.packConfiguration.getPackList().contains(pack))
.limit(9 * 3)
.map(pack -> {
ItemBuilder stack = ItemBuilder.of(pack.buildItem());
if(this.packConfiguration.getPackList().contains(pack)) stack.glint();
return stack.build();
})
.toList(),
this.inventory::setItem
);
IntStream.range(9 * 4, 9 * 5)
.forEach(slot -> this.inventory.setItem(slot, PlaceholderItems.grayStainedGlassPane));
IteratorUtil.iterateListInGlobal(
9 * 5,
IteratorUtil.expandList(
IntStream.range(0, Math.min(this.packConfiguration.getPackList().size(), 9))
.mapToObj(i -> {
PackConfiguration.Pack pack = this.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,
this.unusedSlot
),
this.inventory::setItem
);
}
}

View File

@ -0,0 +1,139 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect.listeners.ClickPackInventoryListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect.listeners.ClosePackInventoryListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect.listeners.SetPacksOnJoinListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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 = this.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")
);
this.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(this.getPackConfigurationForPlayer(player), player);
player.openInventory(packInventory.getInventory());
this.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.common.appliances.metaGameplay.packSelect;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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.common.appliances.metaGameplay.packSelect;
import eu.mhsl.craftattack.core.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;
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.common.appliances.metaGameplay.packSelect.listeners;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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(this.getAppliance().isNotPackInventory(player, event.getInventory())) return;
event.setCancelled(true);
this.getAppliance().openInventories.get(player).handleClick(
event.getCurrentItem(),
event.getSlot(),
event.getClick()
);
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect.listeners;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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(this.getAppliance().isNotPackInventory(player, event.getInventory())) return;
this.getAppliance().openInventories.remove(player);
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.packSelect.listeners;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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(),
() -> this.getAppliance().setPack(event.getPlayer(), this.getAppliance().getPackConfigurationForPlayer(event.getPlayer()))
);
}
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.playtime;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.core.util.text.DataSizeConverter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
public class Playtime extends Appliance {
public Component getFormattedPlaytime(OfflinePlayer player) {
int playtimeInTicks = player.getStatistic(Statistic.PLAY_ONE_MINUTE);
String playtime = DataSizeConverter.formatSecondsToHumanReadable(playtimeInTicks / Ticks.TICKS_PER_SECOND);
return Component.text()
.append(Component.text("Der Spieler ", NamedTextColor.GRAY))
.append(Component.text(Objects.requireNonNull(player.getName())))
.append(Component.text(" hat eine Spielzeit von ", NamedTextColor.GRAY))
.append(Component.text(playtime, NamedTextColor.GOLD))
.build();
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(new PlaytimeCommand());
}
}

View File

@ -0,0 +1,31 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.playtime;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
class PlaytimeCommand extends ApplianceCommand.PlayerChecked<Playtime> {
public PlaytimeCommand() {
super("playtime");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
String playerName = args.length == 1 ? args[0] : this.getPlayer().getName();
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
OfflinePlayer player = Bukkit.getOfflinePlayer(playerName);
if(!player.hasPlayedBefore()) {
sender.sendMessage(Component.text("Der Spieler existiert nicht!", NamedTextColor.RED));
return;
}
sender.sendMessage(this.getAppliance().getFormattedPlaytime(player));
});
}
}

View File

@ -0,0 +1,194 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands.PrivateMessageCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands.PrivateReplyCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
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.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
public class PrivateMessage extends Appliance {
public final int targetChangeTimeoutSeconds = 30;
public final int conversationTimeoutMinutes = 30;
private record Conversation(UUID target, Long lastSet) {
}
private final Map<Player, List<Conversation>> replyMapping = new WeakHashMap<>();
public void reply(Player sender, String message) {
this.replyMapping.computeIfAbsent(sender, player -> new ArrayList<>());
List<Conversation> replyList = this.replyMapping.get(sender);
List<Conversation> tooOldConversations = replyList.stream()
.filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (this.conversationTimeoutMinutes * 60 * 1000))
.toList();
replyList.removeAll(tooOldConversations);
if(replyList.isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
Conversation youngestEntry = replyList.stream()
.max(Comparator.comparingLong(o -> o.lastSet))
.orElse(replyList.getLast());
if(message.isBlank()) {
Player currentTargetPlayer = Bukkit.getPlayer(youngestEntry.target());
Component currentTargetComponent = currentTargetPlayer != null
? Main.instance().getAppliance(ChatMessages.class).getReportablePlayerName(currentTargetPlayer)
: Component.text("niemandem.");
sender.sendMessage(
privatePrefix
.append(Component.text("Du schreibst aktuell mit ", NamedTextColor.GRAY))
.append(currentTargetComponent)
);
return;
}
List<Conversation> oldConversations = replyList.stream()
.filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (this.targetChangeTimeoutSeconds * 1000))
.toList();
if(oldConversations.contains(youngestEntry) || replyList.size() == 1) {
Player target = Bukkit.getPlayer(youngestEntry.target());
if(target == null)
throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
replyList.clear();
this.sendWhisper(sender, new ResolvedPmUserArguments(target, message));
return;
}
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
component.append(
Component.newline()
.append(privatePrefix)
.append(Component.text("Das Ziel für /r hat sich bei dir in den letzten ", NamedTextColor.RED))
.append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds), NamedTextColor.RED))
.append(Component.text(" Sekunden geändert. Wer soll deine Nachricht erhalten? ", NamedTextColor.RED))
.appendNewline()
.appendNewline()
);
if(!oldConversations.isEmpty()) {
Conversation youngestOldConversation = oldConversations.stream()
.max(Comparator.comparingLong(o -> o.lastSet))
.orElse(oldConversations.getLast());
replyList.removeAll(oldConversations);
replyList.add(youngestOldConversation);
}
List<String> playerNames = replyList.stream()
.map(conversation -> Bukkit.getOfflinePlayer(conversation.target()).getName())
.distinct()
.toList();
playerNames.forEach(playerName -> component.append(
Component.text("[")
.append(Component.text(playerName, NamedTextColor.GOLD))
.append(Component.text("]"))
.clickEvent(ClickEvent.runCommand(String.format("/msg %s %s", playerName, message)))
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
.append(Component.text(" "))
);
component.appendNewline();
sender.sendMessage(component.build());
}
public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
Conversation newReceiverConversation = new Conversation(
sender.getUniqueId(),
System.currentTimeMillis()
);
if(this.replyMapping.get(userArguments.receiver) != null) {
List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
.filter(conversation -> conversation.target() == sender.getUniqueId())
.toList();
this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
} else {
this.replyMapping.put(
userArguments.receiver,
new ArrayList<>()
);
}
this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
List<Conversation> senderConversationList = new ArrayList<>();
senderConversationList.add(
new Conversation(
userArguments.receiver.getUniqueId(),
System.currentTimeMillis()
)
);
this.replyMapping.put(
sender,
senderConversationList
);
ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
sender.sendMessage(
Component.text()
.append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", userArguments.receiver.getName()))))
.append(sender.displayName())
.append(Component.text(" zu ", NamedTextColor.GRAY))
.append(chatMessages.getReportablePlayerName(userArguments.receiver))
.append(Component.text(" > ", NamedTextColor.GRAY))
.append(Component.text(userArguments.message))
);
userArguments.receiver.sendMessage(
Component.text()
.append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", sender.getName()))))
.append(chatMessages.getReportablePlayerName(sender))
.append(Component.text(" zu ", NamedTextColor.GRAY))
.append(userArguments.receiver.displayName())
.append(Component.text(" > ", NamedTextColor.GRAY))
.append(Component.text(userArguments.message))
);
}
public record ResolvedPmUserArguments(Player receiver, String message) {
}
public ResolvedPmUserArguments resolveImplicit(String[] args) {
if(args.length < 2)
throw new ApplianceCommand.Error("Es muss ein Spieler sowie eine Nachricht angegeben werden.");
List<String> arguments = List.of(args);
Player targetPlayer = Bukkit.getPlayer(arguments.getFirst());
if(targetPlayer == null)
throw new ApplianceCommand.Error(String.format("Der Spieler %s konnte nicht gefunden werden.", arguments.getFirst()));
String message = arguments.stream().skip(1).collect(Collectors.joining(" "));
return new ResolvedPmUserArguments(targetPlayer, message);
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new PrivateMessageCommand(),
new PrivateReplyCommand()
);
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.PrivateMessage;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class PrivateMessageCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
public PrivateMessageCommand() {
super("msg");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
this.getAppliance().sendWhisper(this.getPlayer(), this.getAppliance().resolveImplicit(args));
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.commands;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.privateMessage.PrivateMessage;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class PrivateReplyCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
public PrivateReplyCommand() {
super("r");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
this.getAppliance().reply(this.getPlayer(), String.join(" ", args));
}
}

View File

@ -0,0 +1,177 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.api.client.ReqResp;
import eu.mhsl.craftattack.core.api.client.repositories.ReportRepository;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
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.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
public class Report extends Appliance {
public static Component helpText() {
return Component.text()
.appendNewline()
.append(Component.text(" Um einen Spieler zu melden, verwende ", NamedTextColor.GRAY)).appendNewline()
.append(Component.text("/report", NamedTextColor.GOLD)).appendNewline()
.append(Component.text("oder", NamedTextColor.GRAY)).appendNewline()
.append(Component.text("/report <spieler> [grund]", NamedTextColor.GOLD)).appendNewline()
.build();
}
public Report() {
super("report");
}
public void reportToUnknown(@NotNull Player issuer) {
ReportRepository.ReportCreationInfo request = new ReportRepository.ReportCreationInfo(issuer.getUniqueId(), null, "");
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.createReport(issuer, request)
);
}
public void reportToKnown(@NotNull Player issuer, @NotNull String targetUsername, @Nullable String reason) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(targetUsername);
if(issuer.getUniqueId().equals(offlinePlayer.getUniqueId())) {
issuer.sendMessage(Component.text("Du kannst dich nicht selbst reporten.", NamedTextColor.RED));
return;
}
ReportRepository.ReportCreationInfo request = new ReportRepository.ReportCreationInfo(
issuer.getUniqueId(),
offlinePlayer.getUniqueId(),
Optional.ofNullable(reason).orElse("")
);
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.createReport(issuer, request)
);
}
private void createReport(Player issuer, ReportRepository.ReportCreationInfo reportRequest) {
ReqResp<ReportRepository.ReportUrl> createdReport = this.queryRepository(ReportRepository.class)
.createReport(reportRequest);
switch(createdReport.status()) {
case 201:
issuer.sendMessage(
Component.text()
.append(Component.text("\\/".repeat(20), NamedTextColor.DARK_GRAY))
.appendNewline()
.append(Component.text("⚠ Der Report muss über den folgenden Link fertiggestellt werden!", NamedTextColor.GOLD))
.appendNewline()
.appendNewline()
.append(
Component
.text(createdReport.data().url(), NamedTextColor.GRAY) // URL mit Weltkugel-Emoji
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.data().url()))
)
.appendNewline()
.appendNewline()
.append(Component.text("Ohne das Fertigstellen des Reports wird dieser nicht bearbeitet!", NamedTextColor.DARK_RED))
.appendNewline()
.append(Component.text("/\\".repeat(20), NamedTextColor.DARK_GRAY))
);
break;
case 400:
issuer.sendMessage(
Component.text()
.append(Component.text("Der angegebene Nutzer ist in unserem System nicht bekannt.", NamedTextColor.RED))
.appendNewline()
.append(Component.text("Bist du sicher, dass du den Namen richtig geschrieben hast?", NamedTextColor.RED))
.appendNewline()
.append(Component.text("Du kannst dich alternativ jederzeit bei einem Admin melden.", NamedTextColor.GRAY))
);
break;
case 401:
default:
Main.logger().warning("Failed to request Report: " + createdReport.status());
issuer.sendMessage(
Component.text()
.append(Component.text("Interner Serverfehler beim anlegen des Reports.", NamedTextColor.RED))
.appendNewline()
.append(Component.text("Bitte melde dich bei einem Admin!", NamedTextColor.RED))
);
break;
}
}
public void queryReports(Player issuer) {
ReqResp<ReportRepository.PlayerReports> userReports = this.queryRepository(ReportRepository.class)
.queryReports(issuer.getUniqueId());
if(userReports.status() != 200) {
Main.logger().warning("Failed to request Reports: " + userReports.status());
issuer.sendMessage(
Component.text()
.append(Component.text("Interner Serverfehler beim abfragen der Reports.", NamedTextColor.RED))
.appendNewline()
.append(Component.text("Bitte melde dich bei einem Admin!", NamedTextColor.RED))
);
return;
}
List<ReportRepository.PlayerReports.Report> reports = userReports
.data()
.from_self()
.stream()
.filter(report -> !report.draft())
.toList()
.reversed();
if(reports.isEmpty()) {
issuer.sendMessage(
Component.text()
.append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
.appendNewline()
.append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
);
return;
}
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text()
.append(Component.newline())
.append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD))
.appendNewline();
reports.forEach(report -> {
component
.append(Component.text(" - ", NamedTextColor.WHITE))
.append(
report.reported() != null
? Component.text(report.reported().username(), NamedTextColor.WHITE)
: Component.text("Unbekannt", NamedTextColor.YELLOW)
)
.append(Component.text(String.format(": %s", report.subject()), NamedTextColor.GRAY))
.clickEvent(ClickEvent.openUrl(report.url()))
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um den Report einzusehen.", NamedTextColor.GOLD)));
component.appendNewline();
});
issuer.sendMessage(component.build());
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new ReportCommand(),
new ReportsCommand()
);
}
}

View File

@ -0,0 +1,65 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class ReportCommand extends ApplianceCommand.PlayerChecked<Report> {
public ReportCommand() {
super("report");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage(ComponentUtil.pleaseWait());
if(args.length == 0) {
this.getAppliance().reportToUnknown(this.getPlayer());
}
if(args.length == 1) {
this.getAppliance().reportToKnown(this.getPlayer(), args[0], null);
}
if(args.length > 1) {
this.getAppliance().reportToKnown(this.getPlayer(), args[0], Arrays.stream(args).skip(1).collect(Collectors.joining(" ")));
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
List<String> response = new ArrayList<>();
if(args.length == 1) {
response = Stream.concat(
Bukkit.getOnlinePlayers().stream().map(Player::getName),
Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName)
).toList();
}
if(args.length == 2) {
response = List.of(
"Griefing",
"Diebstahl",
"Beleidigung",
"Hacking",
"Andere Regelverstöße",
" "
);
}
return super.tabCompleteReducer(response, args);
}
}

View File

@ -0,0 +1,24 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
class ReportsCommand extends ApplianceCommand.PlayerChecked<Report> {
public ReportsCommand() {
super("reports");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage(ComponentUtil.pleaseWait());
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.getAppliance().queryReports(this.getPlayer())
);
}
}

View File

@ -0,0 +1,5 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings;
public interface CategorizedSetting {
SettingCategory category();
}

View File

@ -0,0 +1,7 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings;
public enum SettingCategory {
Gameplay,
Visuals,
Misc,
}

View File

@ -0,0 +1,185 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.core.appliance.Appliance;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.Setting;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.listeners.OpenSettingsShortcutListener;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.listeners.SettingsInventoryListener;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
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,
ShowJoinAndLeaveMessages,
EnablePortableCrafting,
EnableSettingsShortcut,
AutoShulker,
SignEdit,
HotbarReplacer,
ChatMentions,
DoubleDoors,
KnockDoors,
}
public static Settings instance() {
if(settingsInstance != null) return settingsInstance;
Settings.settingsInstance = Main.instance().getAppliance(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) {
}
private final WeakHashMap<Player, OpenSettingsInventory> openSettingsInventories = new WeakHashMap<>();
private final WeakHashMap<Player, List<Setting<?>>> settingsCache = new WeakHashMap<>();
private List<Setting<?>> getSettings(Player player) {
if(this.settingsCache.containsKey(player)) return this.settingsCache.get(player);
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());
this.settingsCache.put(player, settings);
return settings;
}
public <T> T getSetting(Player player, Key key, Class<T> clazz) {
Setting<?> setting = this.getSettings(player).stream()
.filter(s -> Objects.equals(s.getKey(), key))
.findFirst()
.orElseThrow();
if(!clazz.equals(setting.dataType()))
throw new IllegalStateException("Tried to retrieve Setting with Datatype " + clazz.getSimpleName() + " but expected " + setting.dataType().getSimpleName());
if(!clazz.isInstance(setting.state()))
throw new ClassCastException(clazz.getSimpleName() + " is not an instance of " + setting.dataType().getSimpleName());
return clazz.cast(setting.state());
}
public void openSettings(Player player) {
List<Setting<?>> settings = this.getSettings(player);
Inventory inventory = Bukkit.createInventory(null, this.calculateInvSize(settings), Component.text("Einstellungen"));
AtomicInteger row = new AtomicInteger(0);
Arrays.stream(SettingCategory.values())
.forEach(category -> {
List<Setting<?>> categorizedSettings = settings.stream()
.filter(setting -> setting instanceof CategorizedSetting)
.filter(setting -> ((CategorizedSetting) setting).category().equals(category))
.toList();
for(int i = 0; i < categorizedSettings.size(); i++) {
int slot = row.get() * 9 + i % 9;
inventory.setItem(slot, categorizedSettings.get(i).buildItem());
if(i % 9 == 8) {
row.incrementAndGet();
}
}
row.incrementAndGet();
});
List<Setting<?>> uncategorizedSettings = settings.stream()
.filter(setting -> !(setting instanceof CategorizedSetting))
.toList();
for(int i = 0; i < uncategorizedSettings.size(); i++) {
int slot = row.get() * 9 + i % 9;
inventory.setItem(slot, uncategorizedSettings.get(i).buildItem());
if(i % 9 == 8) {
row.incrementAndGet();
}
}
player.openInventory(inventory);
this.openSettingsInventories.put(player, new OpenSettingsInventory(inventory, settings));
}
private int calculateInvSize(List<Setting<?>> settings) {
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)
.map(setting -> (CategorizedSetting) setting)
.filter(categorizedSetting -> categorizedSetting.category().equals(settingCategory))
.count())
.map(itemCount -> itemCount + countOfUncategorized)
.map(itemCount -> (int) Math.ceil((double) itemCount / 9))
.reduce(Integer::sum)
.orElse(1) * 9;
}
public void onSettingsClose(Player player) {
if(!this.openSettingsInventories.containsKey(player)) return;
this.openSettingsInventories.remove(player);
player.updateInventory();
}
public boolean hasSettingsNotOpen(Player player) {
return !this.openSettingsInventories.containsKey(player);
}
public OpenSettingsInventory getOpenInventory(Player player) {
if(this.hasSettingsNotOpen(player))
throw new RuntimeException("Cannot retrieve data from closed Settings inventory!");
return this.openSettingsInventories.get(player);
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(
new SettingsInventoryListener(),
new OpenSettingsShortcutListener()
);
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(new SettingsCommand());
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings;
import eu.mhsl.craftattack.core.appliance.ApplianceCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
class SettingsCommand extends ApplianceCommand.PlayerChecked<Settings> {
public SettingsCommand() {
super("settings");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
this.getAppliance().openSettings(this.getPlayer());
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes.BoolSetting;
import org.bukkit.Material;
public class SettingsShortcutSetting extends BoolSetting implements CategorizedSetting {
public SettingsShortcutSetting() {
super(Settings.Key.EnableSettingsShortcut);
}
@Override
protected String title() {
return "Einstellungen mit 'Shift + F' öffnen";
}
@Override
protected String description() {
return "Wenn aktiviert öffnet sich dieses Einstellungsmenü durch das Drücken von 'Shift + F' im Spiel (Sneaken und Hand-Wechsel)";
}
@Override
protected Material icon() {
return Material.COMPARATOR;
}
@Override
protected Boolean defaultValue() {
return false;
}
@Override
public SettingCategory category() {
return SettingCategory.Misc;
}
}

View File

@ -0,0 +1,52 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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(this.title(), NamedTextColor.WHITE));
meta.lore(this.buildDescription(this.description()));
return meta;
}
@Override
protected void change(Player player, ClickType clickType) {
this.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

@ -0,0 +1,64 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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;
import org.bukkit.persistence.PersistentDataType;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public abstract class BoolSetting extends Setting<Boolean> {
public BoolSetting(Settings.Key key) {
super(key);
}
@Override
public void fromStorage(PersistentDataContainer container) {
this.state = container.has(this.getNamespacedKey())
? Objects.requireNonNull(container.get(this.getNamespacedKey(), PersistentDataType.BOOLEAN))
: this.defaultValue();
}
@Override
protected void toStorage(PersistentDataContainer container, Boolean value) {
container.set(this.getNamespacedKey(), PersistentDataType.BOOLEAN, value);
}
@Override
public ItemMeta buildMeta(ItemMeta meta) {
meta.displayName(Component.text(this.title(), NamedTextColor.WHITE));
List<Component> lore = new ArrayList<>(List.of(
Component.empty()
.append(Component.text("Status: ", NamedTextColor.DARK_GRAY))
.append(Component.text(
this.state ? "Aktiviert" : "Deaktiviert",
this.state ? NamedTextColor.GREEN : NamedTextColor.RED)
),
Component.empty()
));
lore.addAll(this.buildDescription(this.description()));
meta.lore(lore);
return meta;
}
@Override
protected void change(Player player, ClickType clickType) {
this.state = !this.state;
}
@Override
public Class<?> dataType() {
return Boolean.class;
}
@Override
public Boolean state() {
return this.state;
}
}

View File

@ -0,0 +1,153 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes;
import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.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;
import org.bukkit.persistence.PersistentDataType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;
public abstract class MultiBoolSetting<T> extends Setting<T> {
private String cursorPosition;
public MultiBoolSetting(Settings.Key key) {
super(key);
}
@Retention(RetentionPolicy.RUNTIME)
public @interface DisplayName {
String value();
}
@Override
public ItemMeta buildMeta(ItemMeta meta) {
record SettingField(String name, String displayName, Boolean value) {
}
meta.displayName(Component.text(this.title(), NamedTextColor.WHITE));
List<Component> lore = new ArrayList<>();
lore.add(Component.text("Status: ", NamedTextColor.DARK_GRAY));
lore.addAll(
Arrays.stream(this.state.getClass().getRecordComponents())
.map(component -> {
try {
Method method = this.state.getClass().getDeclaredMethod(component.getName());
Boolean value = (Boolean) method.invoke(this.state);
DisplayName annotation = component.getAnnotation(DisplayName.class);
String displayName = annotation != null
? annotation.value()
: component.getName();
return new SettingField(component.getName(), displayName, value);
} catch(NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.map(field -> {
if(this.cursorPosition == null) this.cursorPosition = field.name;
boolean isSelected = field.name.equals(this.cursorPosition);
return Component.text()
.append(Component.text(
isSelected ? "> " : " ",
isSelected ? NamedTextColor.GREEN : NamedTextColor.GRAY)
)
.append(Component.text(
field.displayName + ": ",
isSelected ? NamedTextColor.DARK_GREEN : NamedTextColor.GRAY)
)
.append(Component.text(
field.value ? "Aktiviert" : "Deaktiviert",
field.value ? NamedTextColor.GREEN : NamedTextColor.RED)
)
.build();
})
.toList()
);
lore.add(Component.empty());
lore.addAll(this.buildDescription(this.description()));
lore.add(Component.empty());
lore.add(Component.text("Linksklick", NamedTextColor.AQUA).append(Component.text(" zum Wählen der Option", NamedTextColor.GRAY)));
lore.add(Component.text("Rechtsklick", NamedTextColor.AQUA).append(Component.text(" zum Ändern des Wertes", NamedTextColor.GRAY)));
meta.lore(lore);
return meta;
}
@Override
protected void change(Player player, ClickType clickType) {
var recordComponents = this.state.getClass().getRecordComponents();
int currentIndex = IntStream.range(0, recordComponents.length)
.filter(i -> recordComponents[i].getName().equals(this.cursorPosition))
.findFirst()
.orElse(-1);
if(clickType.equals(ClickType.LEFT)) {
currentIndex = (currentIndex + 1) % recordComponents.length;
this.cursorPosition = recordComponents[currentIndex].getName();
} else if(clickType.equals(ClickType.RIGHT)) {
try {
Object[] values = Arrays.stream(recordComponents)
.map(rc -> {
try {
return this.state.getClass().getDeclaredMethod(rc.getName()).invoke(this.state);
} catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
})
.toArray();
Method getter = this.state.getClass().getDeclaredMethod(this.cursorPosition);
boolean currentValue = (Boolean) getter.invoke(this.state);
values[currentIndex] = !currentValue;
//noinspection unchecked
this.state = (T) this.state.getClass().getConstructor(
Arrays.stream(recordComponents).map(RecordComponent::getType).toArray(Class[]::new)
).newInstance(values);
} catch(NoSuchMethodException | InvocationTargetException | IllegalAccessException |
InstantiationException e) {
throw new RuntimeException(e);
}
}
}
@Override
protected void fromStorage(PersistentDataContainer container) {
String data = container.has(this.getNamespacedKey())
? Objects.requireNonNull(container.get(this.getNamespacedKey(), PersistentDataType.STRING))
: new Gson().toJson(this.defaultValue());
try {
//noinspection unchecked
this.state = (T) new Gson().fromJson(data, this.dataType());
} catch(Exception e) {
this.state = this.defaultValue();
}
}
@Override
protected void toStorage(PersistentDataContainer container, T value) {
container.set(this.getNamespacedKey(), PersistentDataType.STRING, new Gson().toJson(value));
}
@Override
public T state() {
return this.state;
}
}

View File

@ -0,0 +1,106 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
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;
import org.bukkit.persistence.PersistentDataType;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;
public abstract class SelectSetting extends Setting<SelectSetting.Options.Option> {
private final Options options;
public SelectSetting(Settings.Key key, Options options) {
super(key);
this.options = options;
}
public record Options(List<Option> options) {
public record Option(String name, NamespacedKey key) {
public boolean is(Option other) {
return this.equals(other);
}
}
}
protected abstract Material icon();
protected abstract Options.Option defaultValue();
@Override
public ItemMeta buildMeta(ItemMeta meta) {
meta.displayName(Component.text(this.title(), NamedTextColor.WHITE));
List<Component> lore = new ArrayList<>();
lore.add(Component.text("Status: ", NamedTextColor.DARK_GRAY));
lore.addAll(
this.options.options.stream()
.map(option -> {
boolean isSelected = option.equals(this.state);
return Component.text()
.append(Component.text(isSelected ? "> " : " ", isSelected ? NamedTextColor.GREEN : NamedTextColor.GRAY))
.append(Component.text(option.name, isSelected ? NamedTextColor.DARK_GREEN : NamedTextColor.DARK_GRAY))
.build();
})
.toList()
);
lore.add(Component.empty());
lore.addAll(this.buildDescription(this.description()));
meta.lore(lore);
return meta;
}
@Override
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())
.filter(i -> options.get(i).equals(this.state))
.mapToObj(i -> {
int nextIndex = i + optionModifier;
if(nextIndex >= options.size()) {
return options.getFirst();
} else if(nextIndex < 0) {
return options.getLast();
} else {
return options.get(nextIndex);
}
})
.findFirst()
.orElseThrow();
}
@Override
protected void fromStorage(PersistentDataContainer container) {
String data = container.has(this.getNamespacedKey())
? Objects.requireNonNull(container.get(this.getNamespacedKey(), PersistentDataType.STRING))
: this.defaultValue().key.asString();
this.state = this.options.options.stream()
.filter(option -> option.key.asString().equals(data))
.findFirst()
.orElse(this.defaultValue());
}
@Override
protected void toStorage(PersistentDataContainer container, Options.Option value) {
container.set(this.getNamespacedKey(), PersistentDataType.STRING, value.key.asString());
}
@Override
public Class<?> dataType() {
return Options.Option.class;
}
@Override
public Options.Option state() {
return this.state;
}
}

View File

@ -0,0 +1,66 @@
package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.datatypes;
import eu.mhsl.craftattack.core.Main;
import eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.core.util.text.ComponentUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
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.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import java.util.List;
public abstract class Setting<TDataType> {
TDataType state;
private final Settings.Key key;
public Setting(Settings.Key key) {
this.key = key;
}
public NamespacedKey getNamespacedKey() {
return new NamespacedKey(Main.instance(), this.key.name());
}
public Settings.Key getKey() {
return this.key;
}
public void initializeFromPlayer(Player p) {
this.fromStorage(p.getPersistentDataContainer());
}
public void triggerChange(Player p, ClickType clickType) {
this.change(p, clickType);
this.toStorage(p.getPersistentDataContainer(), this.state());
}
public ItemStack buildItem() {
ItemStack stack = new ItemStack(this.icon(), 1);
stack.setItemMeta(this.buildMeta(stack.getItemMeta()));
return stack;
}
protected List<TextComponent> buildDescription(String description) {
return ComponentUtil.lineBreak(description, 50)
.map(s -> Component.text(s, NamedTextColor.GRAY))
.toList();
}
protected abstract String title();
protected abstract String description();
protected abstract Material icon();
public abstract ItemMeta buildMeta(ItemMeta meta);
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);
public abstract Class<?> dataType();
public abstract TDataType state();
}

Some files were not shown because too many files have changed in this diff Show More