added settings with technicalTab toggle
This commit is contained in:
parent
c01ae32f1f
commit
70058c552d
src/main
java/eu/mhsl/craftattack/spawn
Main.java
appliances
util/text
resources
@ -17,6 +17,7 @@ import eu.mhsl.craftattack.spawn.appliances.help.Help;
|
|||||||
import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit;
|
import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit;
|
||||||
import eu.mhsl.craftattack.spawn.appliances.report.Report;
|
import eu.mhsl.craftattack.spawn.appliances.report.Report;
|
||||||
import eu.mhsl.craftattack.spawn.appliances.restart.Restart;
|
import eu.mhsl.craftattack.spawn.appliances.restart.Restart;
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
|
||||||
import eu.mhsl.craftattack.spawn.appliances.tablist.Tablist;
|
import eu.mhsl.craftattack.spawn.appliances.tablist.Tablist;
|
||||||
import eu.mhsl.craftattack.spawn.appliances.titleClear.TitleClear;
|
import eu.mhsl.craftattack.spawn.appliances.titleClear.TitleClear;
|
||||||
import eu.mhsl.craftattack.spawn.appliances.whitelist.Whitelist;
|
import eu.mhsl.craftattack.spawn.appliances.whitelist.Whitelist;
|
||||||
@ -59,7 +60,8 @@ public final class Main extends JavaPlugin {
|
|||||||
new DisplayName(),
|
new DisplayName(),
|
||||||
new Debug(),
|
new Debug(),
|
||||||
new Fleischerchest(),
|
new Fleischerchest(),
|
||||||
new CustomAdvancements()
|
new CustomAdvancements(),
|
||||||
|
new Settings()
|
||||||
);
|
);
|
||||||
|
|
||||||
Bukkit.getLogger().info("Loading appliances...");
|
Bukkit.getLogger().info("Loading appliances...");
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.appliances.settings;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.appliance.Appliance;
|
||||||
|
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.Setting;
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.settings.TechnicalTablistSetting;
|
||||||
|
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.util.List;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
public class Settings extends Appliance {
|
||||||
|
public enum Key {
|
||||||
|
TechnicalTab,
|
||||||
|
}
|
||||||
|
|
||||||
|
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(settingsCache.containsKey(player)) return settingsCache.get(player);
|
||||||
|
|
||||||
|
List<Setting<?>> settings = List.of(
|
||||||
|
new TechnicalTablistSetting()
|
||||||
|
);
|
||||||
|
|
||||||
|
settings.forEach(setting -> setting.initializeFromPlayer(player));
|
||||||
|
this.settingsCache.put(player, settings);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getSetting(Player player, Key key, Class<T> clazz) {
|
||||||
|
Setting<?> setting = getSettings(player).stream()
|
||||||
|
.filter(s -> s.getKey().equals(key))
|
||||||
|
.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) {
|
||||||
|
Inventory inventory = Bukkit.createInventory(null, 9, Component.text("Einstellungen"));
|
||||||
|
List<Setting<?>> settings = getSettings(player);
|
||||||
|
settings.forEach(setting -> inventory.addItem(setting.buildItem()));
|
||||||
|
player.openInventory(inventory);
|
||||||
|
this.openSettingsInventories.put(player, new OpenSettingsInventory(inventory, settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSettingsClose(Player player) {
|
||||||
|
if(!openSettingsInventories.containsKey(player)) return;
|
||||||
|
openSettingsInventories.remove(player);
|
||||||
|
player.updateInventory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSettingsOpen(Player player) {
|
||||||
|
return this.openSettingsInventories.containsKey(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenSettingsInventory getOpenInventory(Player player) {
|
||||||
|
if(!hasSettingsOpen(player)) throw new RuntimeException("Cannot retrieve data from closed Settings inventory!");
|
||||||
|
return this.openSettingsInventories.get(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<Listener> eventHandlers() {
|
||||||
|
return List.of(new SettingsInventoryListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull List<ApplianceCommand<?>> commands() {
|
||||||
|
return List.of(new SettingsCommand());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.appliances.settings;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public 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 {
|
||||||
|
getAppliance().openSettings(getPlayer());
|
||||||
|
}
|
||||||
|
}
|
30
src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingsInventoryListener.java
Normal file
30
src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/SettingsInventoryListener.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.appliances.settings;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||||
|
|
||||||
|
public class SettingsInventoryListener extends ApplianceListener<Settings> {
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
Player player = (Player) event.getWhoClicked();
|
||||||
|
if(!getAppliance().hasSettingsOpen(player)) return;
|
||||||
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
Settings.OpenSettingsInventory openInventory = getAppliance().getOpenInventory(player);
|
||||||
|
openInventory.settings().stream()
|
||||||
|
.filter(setting -> setting.buildItem().equals(event.getCurrentItem()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(setting -> {
|
||||||
|
setting.triggerChange(player);
|
||||||
|
openInventory.inventory().setItem(event.getSlot(), setting.buildItem());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClose(InventoryCloseEvent event) {
|
||||||
|
getAppliance().onSettingsClose((Player) event.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
59
src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/BoolSetting.java
Normal file
59
src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/BoolSetting.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.appliances.settings.datatypes;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class BoolSetting extends Setting<Boolean> {
|
||||||
|
private boolean state;
|
||||||
|
|
||||||
|
public BoolSetting(Settings.Key key) {
|
||||||
|
super(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String title();
|
||||||
|
protected abstract String description();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fromStorage(PersistentDataContainer container) {
|
||||||
|
this.state = Boolean.TRUE.equals(container.get(getNamespacedKey(), PersistentDataType.BOOLEAN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void toStorage(PersistentDataContainer container, Boolean value) {
|
||||||
|
container.set(getNamespacedKey(), PersistentDataType.BOOLEAN, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemMeta buildMeta(ItemMeta meta) {
|
||||||
|
meta.displayName(Component.text(title(), NamedTextColor.WHITE));
|
||||||
|
meta.lore(List.of(
|
||||||
|
Component.empty()
|
||||||
|
.append(Component.text("Status: ", NamedTextColor.DARK_GRAY))
|
||||||
|
.append(Component.text(this.state ? "An" : "Aus", this.state ? NamedTextColor.GREEN : NamedTextColor.RED)),
|
||||||
|
Component.empty(),
|
||||||
|
Component.text(description(), NamedTextColor.GRAY)
|
||||||
|
));
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void change() {
|
||||||
|
this.state = !this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> dataType() {
|
||||||
|
return Boolean.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean state() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.appliances.settings.datatypes;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.Main;
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
|
|
||||||
|
public abstract class Setting<TDataType> {
|
||||||
|
private final Settings.Key key;
|
||||||
|
|
||||||
|
public Setting(Settings.Key key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamespacedKey getNamespacedKey() {
|
||||||
|
return new NamespacedKey(Main.instance(), key.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Settings.Key getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeFromPlayer(Player p) {
|
||||||
|
fromStorage(p.getPersistentDataContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void triggerChange(Player p) {
|
||||||
|
this.change();
|
||||||
|
toStorage(p.getPersistentDataContainer(), this.state());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemStack buildItem() {
|
||||||
|
ItemStack stack = new ItemStack(icon(), 1);
|
||||||
|
stack.setItemMeta(buildMeta(stack.getItemMeta()));
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Material icon();
|
||||||
|
public abstract ItemMeta buildMeta(ItemMeta meta);
|
||||||
|
protected abstract void change();
|
||||||
|
protected abstract void fromStorage(PersistentDataContainer container);
|
||||||
|
protected abstract void toStorage(PersistentDataContainer container, TDataType value);
|
||||||
|
public abstract Class<?> dataType();
|
||||||
|
public abstract TDataType state();
|
||||||
|
}
|
26
src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/TechnicalTablistSetting.java
Normal file
26
src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/TechnicalTablistSetting.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package eu.mhsl.craftattack.spawn.appliances.settings.settings;
|
||||||
|
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.BoolSetting;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
public class TechnicalTablistSetting extends BoolSetting {
|
||||||
|
public TechnicalTablistSetting() {
|
||||||
|
super(Settings.Key.TechnicalTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String title() {
|
||||||
|
return "Technische Informationen";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String description() {
|
||||||
|
return "Zeige erweiterte Informationen und Statistiken in der Tabliste an";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Material icon() {
|
||||||
|
return Material.COMMAND_BLOCK_MINECART;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package eu.mhsl.craftattack.spawn.appliances.tablist;
|
|||||||
import eu.mhsl.craftattack.spawn.Main;
|
import eu.mhsl.craftattack.spawn.Main;
|
||||||
import eu.mhsl.craftattack.spawn.appliance.Appliance;
|
import eu.mhsl.craftattack.spawn.appliance.Appliance;
|
||||||
import eu.mhsl.craftattack.spawn.appliances.report.Report;
|
import eu.mhsl.craftattack.spawn.appliances.report.Report;
|
||||||
|
import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
|
||||||
import eu.mhsl.craftattack.spawn.util.statistics.NetworkMonitor;
|
import eu.mhsl.craftattack.spawn.util.statistics.NetworkMonitor;
|
||||||
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
|
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
|
||||||
import eu.mhsl.craftattack.spawn.util.IteratorUtil;
|
import eu.mhsl.craftattack.spawn.util.IteratorUtil;
|
||||||
@ -20,7 +21,6 @@ import java.util.List;
|
|||||||
|
|
||||||
|
|
||||||
public class Tablist extends Appliance {
|
public class Tablist extends Appliance {
|
||||||
private final int refreshRate = Ticks.TICKS_PER_SECOND * 3;
|
|
||||||
private final RainbowComponent serverName = new RainbowComponent(" CraftAttack 7 ", 7, 3);
|
private final RainbowComponent serverName = new RainbowComponent(" CraftAttack 7 ", 7, 3);
|
||||||
private NetworkMonitor networkMonitor;
|
private NetworkMonitor networkMonitor;
|
||||||
|
|
||||||
@ -30,12 +30,13 @@ public class Tablist extends Appliance {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
int tabRefreshRate = 3;
|
||||||
this.networkMonitor = new NetworkMonitor(localConfig().getString("interface"), Duration.ofSeconds(1));
|
this.networkMonitor = new NetworkMonitor(localConfig().getString("interface"), Duration.ofSeconds(1));
|
||||||
Bukkit.getScheduler().runTaskTimerAsynchronously(
|
Bukkit.getScheduler().runTaskTimerAsynchronously(
|
||||||
Main.instance(),
|
Main.instance(),
|
||||||
() -> IteratorUtil.onlinePlayers(this::updateHeader),
|
() -> IteratorUtil.onlinePlayers(this::updateHeader),
|
||||||
refreshRate,
|
tabRefreshRate * Ticks.TICKS_PER_SECOND,
|
||||||
refreshRate
|
tabRefreshRate * Ticks.TICKS_PER_SECOND
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,17 +51,23 @@ public class Tablist extends Appliance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateHeader(Player player) {
|
private void updateHeader(Player player) {
|
||||||
player.sendPlayerListHeader(
|
boolean detailedInfo = queryAppliance(Settings.class).getSetting(player, Settings.Key.TechnicalTab, Boolean.class);
|
||||||
Component.newline()
|
Component header = Component.newline()
|
||||||
.append(serverName.getRainbowState()).appendNewline()
|
.append(serverName.getRainbowState()).appendNewline()
|
||||||
.append(Component.text("mhsl.eu", NamedTextColor.GOLD)).appendNewline().appendNewline()
|
.append(Component.text("mhsl.eu", NamedTextColor.GOLD)).appendNewline().appendNewline()
|
||||||
.append(ComponentUtil.getFormattedMSPT()).appendNewline().appendNewline()
|
.append(ComponentUtil.getFormattedTickTimes(detailedInfo)).appendNewline();
|
||||||
|
|
||||||
|
if(detailedInfo) {
|
||||||
|
header = header
|
||||||
|
.appendNewline()
|
||||||
.append(ComponentUtil.getFormattedPing(player)).appendNewline()
|
.append(ComponentUtil.getFormattedPing(player)).appendNewline()
|
||||||
.append(ComponentUtil.getFormattedNetworkStats(
|
.append(ComponentUtil.getFormattedNetworkStats(
|
||||||
this.networkMonitor.getTraffic(),
|
this.networkMonitor.getTraffic(),
|
||||||
this.networkMonitor.getPackets())
|
this.networkMonitor.getPackets())
|
||||||
).appendNewline()
|
).appendNewline();
|
||||||
);
|
}
|
||||||
|
|
||||||
|
player.sendPlayerListHeader(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFooter(Player player) {
|
private void updateFooter(Player player) {
|
||||||
|
@ -2,6 +2,8 @@ package eu.mhsl.craftattack.spawn.util.text;
|
|||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.util.statistics.NetworkMonitor;
|
import eu.mhsl.craftattack.spawn.util.statistics.NetworkMonitor;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.ComponentBuilder;
|
||||||
|
import net.kyori.adventure.text.TextComponent;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@ -39,7 +41,7 @@ public class ComponentUtil {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Component getFormattedMSPT() {
|
public static Component getFormattedTickTimes(boolean detailed) {
|
||||||
long[] times = Bukkit.getServer().getTickTimes();
|
long[] times = Bukkit.getServer().getTickTimes();
|
||||||
float mspt = ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f;
|
float mspt = ((float) Arrays.stream(times).sum() / times.length) * 1.0E-6f;
|
||||||
float roundedMspt = Math.round(mspt * 100f) / 100f;
|
float roundedMspt = Math.round(mspt * 100f) / 100f;
|
||||||
@ -50,12 +52,18 @@ public class ComponentUtil {
|
|||||||
TextColor percentageColor = ColorUtil.mapGreenToRed(loadPercentage, 80, 100, true);
|
TextColor percentageColor = ColorUtil.mapGreenToRed(loadPercentage, 80, 100, true);
|
||||||
TextColor tpsColor = ColorUtil.mapGreenToRed(roundedTPS, 15, 20, false);
|
TextColor tpsColor = ColorUtil.mapGreenToRed(roundedTPS, 15, 20, false);
|
||||||
|
|
||||||
return Component.text()
|
ComponentBuilder<TextComponent, TextComponent.Builder> tickTimes = Component.text()
|
||||||
.append(Component.text("Serverlast: ", NamedTextColor.GRAY))
|
.append(Component.text("Serverlast: ", NamedTextColor.GRAY))
|
||||||
.append(Component.text(loadPercentage + "% ", percentageColor))
|
.append(Component.text(loadPercentage + "% ", percentageColor))
|
||||||
.appendNewline()
|
.appendNewline();
|
||||||
|
|
||||||
|
if(detailed) {
|
||||||
|
tickTimes
|
||||||
.append(Component.text(roundedMspt + "mspt", msptColor))
|
.append(Component.text(roundedMspt + "mspt", msptColor))
|
||||||
.append(Component.text(" | ", NamedTextColor.GRAY))
|
.append(Component.text(" | ", NamedTextColor.GRAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tickTimes
|
||||||
.append(Component.text(roundedTPS + "tps", tpsColor))
|
.append(Component.text(roundedTPS + "tps", tpsColor))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -36,3 +36,4 @@ commands:
|
|||||||
kick:
|
kick:
|
||||||
panicBan:
|
panicBan:
|
||||||
vogelfrei:
|
vogelfrei:
|
||||||
|
settings:
|
Loading…
x
Reference in New Issue
Block a user