geforkt von Mirrors/Paper
abstract custom set tags, add entity tags
Dieser Commit ist enthalten in:
Ursprung
c7667378e7
Commit
5869669498
@ -6,12 +6,14 @@ Subject: [PATCH] Add Material Tags
|
||||
This adds a bunch of useful and missing Tags to be able to identify items that
|
||||
are related to each other by a trait.
|
||||
|
||||
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
|
||||
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/MaterialSetTag.java b/src/main/java/com/destroystokyo/paper/MaterialSetTag.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..c91ea2a0679a7f3a5627b5a008e0b39df3332889
|
||||
index 0000000000000000000000000000000000000000..a02a02aa0c87e0f0ed9e509e4dcab01565b3d92a
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/destroystokyo/paper/MaterialSetTag.java
|
||||
@@ -0,0 +1,190 @@
|
||||
@@ -0,0 +1,97 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License
|
||||
+ */
|
||||
@ -19,10 +21,9 @@ index 0000000000000000000000000000000000000000..c91ea2a0679a7f3a5627b5a008e0b39d
|
||||
+package com.destroystokyo.paper;
|
||||
+
|
||||
+import com.google.common.collect.Lists;
|
||||
+import com.google.common.collect.Sets;
|
||||
+import io.papermc.paper.tag.BaseTag;
|
||||
+import org.bukkit.Material;
|
||||
+import org.bukkit.NamespacedKey;
|
||||
+import org.bukkit.Tag;
|
||||
+import org.bukkit.block.Block;
|
||||
+import org.bukkit.block.BlockState;
|
||||
+import org.bukkit.block.data.BlockData;
|
||||
@ -36,10 +37,7 @@ index 0000000000000000000000000000000000000000..c91ea2a0679a7f3a5627b5a008e0b39d
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+import org.jetbrains.annotations.Nullable;
|
||||
+
|
||||
+public class MaterialSetTag implements Tag<Material> {
|
||||
+
|
||||
+ private final NamespacedKey key;
|
||||
+ private final Set<Material> materials;
|
||||
+public class MaterialSetTag extends BaseTag<Material, MaterialSetTag> {
|
||||
+
|
||||
+ /**
|
||||
+ * @deprecated Use NamespacedKey version of constructor
|
||||
@ -74,103 +72,23 @@ index 0000000000000000000000000000000000000000..c91ea2a0679a7f3a5627b5a008e0b39d
|
||||
+ }
|
||||
+
|
||||
+ public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Collection<Material> materials) {
|
||||
+ this.key = key != null ? key : NamespacedKey.randomKey();
|
||||
+ this.materials = Sets.newEnumSet(materials, Material.class);
|
||||
+ this(key != null ? key : NamespacedKey.randomKey(), materials, ((Predicate<Material>) Material::isLegacy).negate());
|
||||
+ }
|
||||
+
|
||||
+ public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Collection<Material> materials, @NotNull Predicate<Material>...globalPredicates) {
|
||||
+ super(Material.class, key != null ? key : NamespacedKey.randomKey(), materials, globalPredicates);
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ @Override
|
||||
+ public NamespacedKey getKey() {
|
||||
+ return key;
|
||||
+ protected Set<Material> getAllPossibleValues() {
|
||||
+ return Stream.of(Material.values()).collect(Collectors.toSet());
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag add(@NotNull Tag<Material>... tags) {
|
||||
+ for (Tag<Material> tag : tags) {
|
||||
+ add(tag.getValues());
|
||||
+ }
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag add(@NotNull MaterialSetTag... tags) {
|
||||
+ for (Tag<Material> tag : tags) {
|
||||
+ add(tag.getValues());
|
||||
+ }
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag add(@NotNull Material... material) {
|
||||
+ this.materials.addAll(Lists.newArrayList(material));
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag add(@NotNull Collection<Material> materials) {
|
||||
+ this.materials.addAll(materials);
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag contains(@NotNull String with) {
|
||||
+ return add(mat -> mat.name().contains(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag endsWith(@NotNull String with) {
|
||||
+ return add(mat -> mat.name().endsWith(with));
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag startsWith(@NotNull String with) {
|
||||
+ return add(mat -> mat.name().startsWith(with));
|
||||
+ }
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag add(@NotNull Predicate<Material> filter) {
|
||||
+ add(Stream.of(Material.values()).filter(((Predicate<Material>) Material::isLegacy).negate()).filter(filter).collect(Collectors.toList()));
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag not(@NotNull MaterialSetTag tags) {
|
||||
+ not(tags.getValues());
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag not(@NotNull Material... material) {
|
||||
+ this.materials.removeAll(Lists.newArrayList(material));
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag not(@NotNull Collection<Material> materials) {
|
||||
+ this.materials.removeAll(materials);
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag not(@NotNull Predicate<Material> filter) {
|
||||
+ not(Stream.of(Material.values()).filter(((Predicate<Material>) Material::isLegacy).negate()).filter(filter).collect(Collectors.toList()));
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag notEndsWith(@NotNull String with) {
|
||||
+ return not(mat -> mat.name().endsWith(with));
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag notStartsWith(@NotNull String with) {
|
||||
+ return not(mat -> mat.name().startsWith(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public Set<Material> getValues() {
|
||||
+ return this.materials;
|
||||
+ protected String getName(@NotNull Material value) {
|
||||
+ return value.name();
|
||||
+ }
|
||||
+
|
||||
+ public boolean isTagged(@NotNull BlockData block) {
|
||||
@ -190,16 +108,7 @@ index 0000000000000000000000000000000000000000..c91ea2a0679a7f3a5627b5a008e0b39d
|
||||
+ }
|
||||
+
|
||||
+ public boolean isTagged(@NotNull Material material) {
|
||||
+ return this.materials.contains(material);
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public MaterialSetTag ensureSize(@NotNull String label, int size) {
|
||||
+ long actual = this.materials.stream().filter(((Predicate<Material>) Material::isLegacy).negate()).count();
|
||||
+ if (size != actual) {
|
||||
+ throw new IllegalStateException(label + " - Expected " + size + " materials, got " + actual);
|
||||
+ }
|
||||
+ return this;
|
||||
+ return this.tagged.contains(material);
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/MaterialTags.java b/src/main/java/com/destroystokyo/paper/MaterialTags.java
|
||||
@ -764,6 +673,275 @@ index 0000000000000000000000000000000000000000..3f36165d89ae4aaa153dcb9ddbb8c58a
|
||||
+ .add(Material.TRIDENT, Material.SHIELD, Material.FISHING_ROD, Material.SHEARS, Material.FLINT_AND_STEEL, Material.CARROT_ON_A_STICK, Material.WARPED_FUNGUS_ON_A_STICK)
|
||||
+ .ensureSize("ENCHANTABLE", 65);
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/tag/BaseTag.java b/src/main/java/io/papermc/paper/tag/BaseTag.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..c8777d2298c80c9579635000044e2d0a987cc15b
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/tag/BaseTag.java
|
||||
@@ -0,0 +1,159 @@
|
||||
+package io.papermc.paper.tag;
|
||||
+
|
||||
+import com.google.common.collect.Lists;
|
||||
+import org.bukkit.Keyed;
|
||||
+import org.bukkit.NamespacedKey;
|
||||
+import org.bukkit.Tag;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+import java.util.Collection;
|
||||
+import java.util.EnumSet;
|
||||
+import java.util.HashSet;
|
||||
+import java.util.List;
|
||||
+import java.util.Set;
|
||||
+import java.util.function.Predicate;
|
||||
+import java.util.stream.Collectors;
|
||||
+
|
||||
+public abstract class BaseTag<T extends Keyed, C extends BaseTag<T, C>> implements Tag<T> {
|
||||
+
|
||||
+ protected final NamespacedKey key;
|
||||
+ protected final Set<T> tagged;
|
||||
+ private final List<Predicate<T>> globalPredicates;
|
||||
+
|
||||
+ public BaseTag(@NotNull Class<T> clazz, @NotNull NamespacedKey key, @NotNull Predicate<T> filter) {
|
||||
+ this(clazz, key);
|
||||
+ add(filter);
|
||||
+ }
|
||||
+
|
||||
+ public BaseTag(@NotNull Class<T> clazz, @NotNull NamespacedKey key, @NotNull T...values) {
|
||||
+ this(clazz, key, Lists.newArrayList(values));
|
||||
+ }
|
||||
+
|
||||
+ public BaseTag(@NotNull Class<T> clazz, @NotNull NamespacedKey key, @NotNull Collection<T> values) {
|
||||
+ this(clazz, key, values, o -> true);
|
||||
+ }
|
||||
+
|
||||
+ public BaseTag(@NotNull Class<T> clazz, @NotNull NamespacedKey key, @NotNull Collection<T> values, @NotNull Predicate<T>... globalPredicates) {
|
||||
+ this.key = key != null ? key : NamespacedKey.randomKey();
|
||||
+ this.tagged = clazz.isEnum() ? createEnumSet(clazz) : new HashSet<>();
|
||||
+ this.globalPredicates = Lists.newArrayList(globalPredicates);
|
||||
+ }
|
||||
+
|
||||
+ private <E> Set<E> createEnumSet(Class<E> enumClass) {
|
||||
+ assert enumClass.isEnum();
|
||||
+ return (Set<E>) EnumSet.noneOf((Class<Enum>) enumClass);
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ @Override
|
||||
+ public NamespacedKey getKey() {
|
||||
+ return key;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ @Override
|
||||
+ public Set<T> getValues() {
|
||||
+ return tagged;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isTagged(@NotNull T item) {
|
||||
+ return tagged.contains(item);
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C add(@NotNull Tag<T>...tags) {
|
||||
+ for (Tag<T> tag : tags) {
|
||||
+ add(tag.getValues());
|
||||
+ }
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C add(@NotNull T...values) {
|
||||
+ this.tagged.addAll(Lists.newArrayList(values));
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C add(@NotNull Collection<T> collection) {
|
||||
+ this.tagged.addAll(collection);
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C add(@NotNull Predicate<T> filter) {
|
||||
+ return add(getAllPossibleValues().stream().filter(globalPredicates.stream().reduce(Predicate::or).orElse(t -> true)).filter(filter).collect(Collectors.toSet()));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C contains(@NotNull String with) {
|
||||
+ return add(value -> getName(value).contains(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C endsWith(@NotNull String with) {
|
||||
+ return add(value -> getName(value).endsWith(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C startsWith(@NotNull String with) {
|
||||
+ return add(value -> getName(value).startsWith(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C not(@NotNull Tag<T>...tags) {
|
||||
+ for (Tag<T> tag : tags) {
|
||||
+ not(tag.getValues());
|
||||
+ }
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C not(@NotNull T...values) {
|
||||
+ this.tagged.removeAll(Lists.newArrayList(values));
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C not(@NotNull Collection<T> values) {
|
||||
+ this.tagged.removeAll(values);
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C not(@NotNull Predicate<T> filter) {
|
||||
+ not(getAllPossibleValues().stream().filter(globalPredicates.stream().reduce(Predicate::or).orElse(t -> true)).filter(filter).collect(Collectors.toSet()));
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C notContains(@NotNull String with) {
|
||||
+ return not(value -> getName(value).contains(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C notEndsWith(@NotNull String with) {
|
||||
+ return not(value -> getName(value).endsWith(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C notStartsWith(@NotNull String with) {
|
||||
+ return not(value -> getName(value).startsWith(with));
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ public C ensureSize(@NotNull String label, int size) {
|
||||
+ long actual = this.tagged.stream().filter(globalPredicates.stream().reduce(Predicate::or).orElse(t -> true)).count();
|
||||
+ if (size != actual) {
|
||||
+ throw new IllegalStateException(key.toString() + ": " + label + " - Expected " + size + " values, got " + actual);
|
||||
+ }
|
||||
+ return (C) this;
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ protected abstract Set<T> getAllPossibleValues();
|
||||
+
|
||||
+ @NotNull
|
||||
+ protected abstract String getName(@NotNull T value);
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/tag/EntitySetTag.java b/src/main/java/io/papermc/paper/tag/EntitySetTag.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..c89c4619aaf388197834d98eb95af2f1e93db871
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/tag/EntitySetTag.java
|
||||
@@ -0,0 +1,42 @@
|
||||
+package io.papermc.paper.tag;
|
||||
+
|
||||
+import org.bukkit.NamespacedKey;
|
||||
+import org.bukkit.entity.EntityType;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+import java.util.Collection;
|
||||
+import java.util.Set;
|
||||
+import java.util.function.Predicate;
|
||||
+import java.util.stream.Collectors;
|
||||
+import java.util.stream.Stream;
|
||||
+
|
||||
+public class EntitySetTag extends BaseTag<EntityType, EntitySetTag> {
|
||||
+
|
||||
+ public EntitySetTag(@NotNull NamespacedKey key, @NotNull Predicate<EntityType> filter) {
|
||||
+ super(EntityType.class, key, filter);
|
||||
+ }
|
||||
+
|
||||
+ public EntitySetTag(@NotNull NamespacedKey key, @NotNull EntityType... values) {
|
||||
+ super(EntityType.class, key, values);
|
||||
+ }
|
||||
+
|
||||
+ public EntitySetTag(@NotNull NamespacedKey key, @NotNull Collection<EntityType> values) {
|
||||
+ super(EntityType.class, key, values);
|
||||
+ }
|
||||
+
|
||||
+ public EntitySetTag(@NotNull NamespacedKey key, @NotNull Collection<EntityType> values, @NotNull Predicate<EntityType>... globalPredicates) {
|
||||
+ super(EntityType.class, key, values, globalPredicates);
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ @Override
|
||||
+ protected Set<EntityType> getAllPossibleValues() {
|
||||
+ return Stream.of(EntityType.values()).collect(Collectors.toSet());
|
||||
+ }
|
||||
+
|
||||
+ @NotNull
|
||||
+ @Override
|
||||
+ protected String getName(@NotNull EntityType value) {
|
||||
+ return value.name();
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/tag/EntityTags.java b/src/main/java/io/papermc/paper/tag/EntityTags.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..9266c9d77e2eef7cd717dc729834a190f1fc7c1d
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/tag/EntityTags.java
|
||||
@@ -0,0 +1,50 @@
|
||||
+package io.papermc.paper.tag;
|
||||
+
|
||||
+import org.bukkit.NamespacedKey;
|
||||
+
|
||||
+import static org.bukkit.entity.EntityType.*;
|
||||
+
|
||||
+public class EntityTags {
|
||||
+
|
||||
+ private static NamespacedKey keyFor(String key) {
|
||||
+ //noinspection deprecation
|
||||
+ return new NamespacedKey("paper", key + "_settag");
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Covers undead mobs
|
||||
+ * @see <a href="https://minecraft.gamepedia.com/Mob#Undead_mobs">https://minecraft.gamepedia.com/Mob#Undead_mobs</a>
|
||||
+ */
|
||||
+ public static final EntitySetTag UNDEADS = new EntitySetTag(keyFor("undeads"))
|
||||
+ .add(DROWNED, HUSK, PHANTOM, SKELETON, SKELETON_HORSE, STRAY, WITHER, WITHER_SKELETON, ZOGLIN, ZOMBIE, ZOMBIE_HORSE, ZOMBIE_VILLAGER, ZOMBIFIED_PIGLIN)
|
||||
+ .ensureSize("UNDEADS", 13);
|
||||
+
|
||||
+ /**
|
||||
+ * Covers all horses
|
||||
+ */
|
||||
+ public static final EntitySetTag HORSES = new EntitySetTag(keyFor("horses"))
|
||||
+ .contains("HORSE")
|
||||
+ .ensureSize("HORSES", 3);
|
||||
+
|
||||
+ /**
|
||||
+ * Covers all minecarts
|
||||
+ */
|
||||
+ public static final EntitySetTag MINECARTS = new EntitySetTag(keyFor("minecarts"))
|
||||
+ .contains("MINECART")
|
||||
+ .ensureSize("MINECARTS", 7);
|
||||
+
|
||||
+ /**
|
||||
+ * Covers mobs that split into smaller mobs
|
||||
+ */
|
||||
+ public static final EntitySetTag SPLITTING_MOBS = new EntitySetTag(keyFor("splitting_mobs"))
|
||||
+ .add(SLIME, MAGMA_CUBE)
|
||||
+ .ensureSize("SLIMES", 2);
|
||||
+
|
||||
+ /**
|
||||
+ * Covers all water based mobs
|
||||
+ * @see <a href="https://minecraft.gamepedia.com/Mob#Water-based_mobs">https://minecraft.gamepedia.com/Mob#Water-based_mobs</a>
|
||||
+ */
|
||||
+ public static final EntitySetTag WATER_BASED = new EntitySetTag(keyFor("water_based"))
|
||||
+ .add(DOLPHIN, SQUID, GUARDIAN, ELDER_GUARDIAN, TURTLE, COD, SALMON, PUFFERFISH, TROPICAL_FISH)
|
||||
+ .ensureSize("WATER_BASED", 9);
|
||||
+}
|
||||
diff --git a/src/test/java/com/destroystokyo/paper/MaterialTagsTest.java b/src/test/java/com/destroystokyo/paper/MaterialTagsTest.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..328c51471dc12e81c1a1b643455337b3fef4d14a
|
||||
@ -795,6 +973,36 @@ index 0000000000000000000000000000000000000000..328c51471dc12e81c1a1b643455337b3
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/test/java/io/papermc/paper/EntityTagsTest.java b/src/test/java/io/papermc/paper/EntityTagsTest.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..06bb9d1180361d3d00c699796bbacbce5bef2177
|
||||
--- /dev/null
|
||||
+++ b/src/test/java/io/papermc/paper/EntityTagsTest.java
|
||||
@@ -0,0 +1,24 @@
|
||||
+package io.papermc.paper;
|
||||
+
|
||||
+import com.destroystokyo.paper.MaterialTags;
|
||||
+import io.papermc.paper.tag.EntityTags;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.TestServer;
|
||||
+import org.junit.Test;
|
||||
+
|
||||
+import java.util.logging.Level;
|
||||
+
|
||||
+public class EntityTagsTest {
|
||||
+
|
||||
+ @Test
|
||||
+ public void testInitialize() {
|
||||
+ try {
|
||||
+ TestServer.getInstance();
|
||||
+ EntityTags.HORSES.getValues();
|
||||
+ assert true;
|
||||
+ } catch (Throwable e) {
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e);
|
||||
+ assert false;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/test/java/org/bukkit/TestServer.java b/src/test/java/org/bukkit/TestServer.java
|
||||
index 61993528e6975c38d82213e9b5caf996fe777328..5f9d348241210689eaf41a39ace5948e7a237b12 100644
|
||||
--- a/src/test/java/org/bukkit/TestServer.java
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren