mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-28 20:07:41 +01:00
Implement BossBarViewer on Player (#9332)
* Implement BossBarViewer on Player Author: Riley Park <rileysebastianpark@gmail.com> * Implement BossBar#viewers
This commit is contained in:
parent
8df3a11758
commit
bc6e534738
@ -2051,7 +2051,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
* Represents a player, connected or not
|
||||
*/
|
||||
-public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient {
|
||||
+public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified { // Paper
|
||||
+public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified, net.kyori.adventure.bossbar.BossBarViewer { // Paper
|
||||
+
|
||||
+ // Paper start
|
||||
+ @Override
|
||||
@ -2060,6 +2060,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Gets an unmodifiable view of all known currently active bossbars.
|
||||
+ * <p>
|
||||
+ * <b>This currently only returns bossbars shown to the player via
|
||||
+ * {@link #showBossBar(net.kyori.adventure.bossbar.BossBar)} and does not contain bukkit
|
||||
+ * {@link org.bukkit.boss.BossBar} instances shown to the player.</b>
|
||||
+ *
|
||||
+ * @return an unmodifiable view of all known currently active bossbars
|
||||
+ * @since 4.14.0
|
||||
+ */
|
||||
+ @Override
|
||||
+ @org.jetbrains.annotations.UnmodifiableView @NotNull Iterable<? extends net.kyori.adventure.bossbar.BossBar> activeBossBars();
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the "friendly" name to display of this player.
|
||||
+ *
|
||||
+ * @return the display name
|
||||
@ -2358,25 +2371,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
public void setResourcePack(@NotNull String url, @Nullable byte[] hash, @Nullable String prompt);
|
||||
|
||||
+ // Paper start
|
||||
/**
|
||||
* Request that the player's client download and switch resource packs.
|
||||
* <p>
|
||||
@@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
|
||||
* @param hash The sha1 hash sum of the resource pack file which is used
|
||||
* to apply a cached version of the pack directly without downloading
|
||||
* if it is available. Hast to be 20 bytes long!
|
||||
+ * @param prompt The optional custom prompt message to be shown to client.
|
||||
+ * @throws IllegalArgumentException Thrown if the URL is null.
|
||||
+ * @throws IllegalArgumentException Thrown if the URL is too long. The
|
||||
+ * length restriction is an implementation specific arbitrary value.
|
||||
+ * @throws IllegalArgumentException Thrown if the hash is not 20 bytes
|
||||
+ * long.
|
||||
+ */
|
||||
+ default void setResourcePack(@NotNull String url, byte @Nullable [] hash, net.kyori.adventure.text.@Nullable Component prompt) {
|
||||
+ this.setResourcePack(url, hash, prompt, false);
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
+ /**
|
||||
+ * Request that the player's client download and switch resource packs.
|
||||
+ * <p>
|
||||
@ -2406,16 +2400,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+ * pack correctly.
|
||||
+ * </ul>
|
||||
+ *
|
||||
+ * @deprecated in favour of {@link #setResourcePack(String, byte[], Component, boolean)}
|
||||
+ * @param url The URL from which the client will download the resource
|
||||
+ * pack. The string must contain only US-ASCII characters and should
|
||||
+ * be encoded as per RFC 1738.
|
||||
+ * @param hash The sha1 hash sum of the resource pack file which is used
|
||||
+ * to apply a cached version of the pack directly without downloading
|
||||
+ * if it is available. Hast to be 20 bytes long!
|
||||
* @param force If true, the client will be disconnected from the server
|
||||
* when it declines to use the resource pack.
|
||||
* @throws IllegalArgumentException Thrown if the URL is null.
|
||||
+ * @param prompt The optional custom prompt message to be shown to client.
|
||||
+ * @throws IllegalArgumentException Thrown if the URL is null.
|
||||
+ * @throws IllegalArgumentException Thrown if the URL is too long. The
|
||||
+ * length restriction is an implementation specific arbitrary value.
|
||||
+ * @throws IllegalArgumentException Thrown if the hash is not 20 bytes
|
||||
+ * long.
|
||||
+ */
|
||||
+ default void setResourcePack(@NotNull String url, byte @Nullable [] hash, net.kyori.adventure.text.@Nullable Component prompt) {
|
||||
+ this.setResourcePack(url, hash, prompt, false);
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
/**
|
||||
* Request that the player's client download and switch resource packs.
|
||||
* <p>
|
||||
@@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
|
||||
* pack correctly.
|
||||
* </ul>
|
||||
*
|
||||
+ * @deprecated in favour of {@link #setResourcePack(String, byte[], Component, boolean)}
|
||||
* @param url The URL from which the client will download the resource
|
||||
* pack. The string must contain only US-ASCII characters and should
|
||||
* be encoded as per RFC 1738.
|
||||
@@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
|
||||
* @throws IllegalArgumentException Thrown if the hash is not 20 bytes
|
||||
* long.
|
||||
|
@ -64,8 +64,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
/**
|
||||
* Represents a player, connected or not
|
||||
*/
|
||||
-public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified { // Paper
|
||||
+public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified, com.destroystokyo.paper.network.NetworkClient { // Paper
|
||||
-public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified, net.kyori.adventure.bossbar.BossBarViewer { // Paper
|
||||
+public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, net.kyori.adventure.identity.Identified, net.kyori.adventure.bossbar.BossBarViewer, com.destroystokyo.paper.network.NetworkClient { // Paper
|
||||
|
||||
// Paper start
|
||||
@Override
|
||||
|
@ -102,6 +102,96 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/adventure/BossBarImplementationImpl.java b/src/main/java/io/papermc/paper/adventure/BossBarImplementationImpl.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/adventure/BossBarImplementationImpl.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+package io.papermc.paper.adventure;
|
||||
+
|
||||
+import com.google.common.collect.Collections2;
|
||||
+import java.util.Set;
|
||||
+import java.util.function.Function;
|
||||
+import net.kyori.adventure.bossbar.BossBar;
|
||||
+import net.kyori.adventure.bossbar.BossBarImplementation;
|
||||
+import net.kyori.adventure.bossbar.BossBarViewer;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.minecraft.network.protocol.game.ClientboundBossEventPacket;
|
||||
+import net.minecraft.server.level.ServerBossEvent;
|
||||
+import net.minecraft.server.level.ServerPlayer;
|
||||
+import net.minecraft.world.BossEvent;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+public final class BossBarImplementationImpl implements BossBar.Listener, BossBarImplementation {
|
||||
+ private final BossBar bar;
|
||||
+ private ServerBossEvent vanilla;
|
||||
+
|
||||
+ public BossBarImplementationImpl(final BossBar bar) {
|
||||
+ this.bar = bar;
|
||||
+ }
|
||||
+
|
||||
+ public void playerShow(final CraftPlayer player) {
|
||||
+ if (this.vanilla == null) {
|
||||
+ this.vanilla = new ServerBossEvent(
|
||||
+ PaperAdventure.asVanilla(this.bar.name()),
|
||||
+ PaperAdventure.asVanilla(this.bar.color()),
|
||||
+ PaperAdventure.asVanilla(this.bar.overlay())
|
||||
+ );
|
||||
+ this.vanilla.adventure = this.bar;
|
||||
+ this.bar.addListener(this);
|
||||
+ }
|
||||
+ this.vanilla.addPlayer(player.getHandle());
|
||||
+ }
|
||||
+
|
||||
+ public void playerHide(final CraftPlayer player) {
|
||||
+ if (this.vanilla != null) {
|
||||
+ this.vanilla.removePlayer(player.getHandle());
|
||||
+ if (this.vanilla.getPlayers().isEmpty()) {
|
||||
+ this.bar.removeListener(this);
|
||||
+ this.vanilla = null;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarNameChanged(final @NonNull BossBar bar, final @NonNull Component oldName, final @NonNull Component newName) {
|
||||
+ this.maybeBroadcast(ClientboundBossEventPacket::createUpdateNamePacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarProgressChanged(final @NonNull BossBar bar, final float oldProgress, final float newProgress) {
|
||||
+ this.maybeBroadcast(ClientboundBossEventPacket::createUpdateProgressPacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarColorChanged(final @NonNull BossBar bar, final BossBar.@NonNull Color oldColor, final BossBar.@NonNull Color newColor) {
|
||||
+ this.maybeBroadcast(ClientboundBossEventPacket::createUpdateStylePacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarOverlayChanged(final @NonNull BossBar bar, final BossBar.@NonNull Overlay oldOverlay, final BossBar.@NonNull Overlay newOverlay) {
|
||||
+ this.maybeBroadcast(ClientboundBossEventPacket::createUpdateStylePacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarFlagsChanged(final @NonNull BossBar bar, final @NonNull Set<BossBar.Flag> flagsAdded, final @NonNull Set<BossBar.Flag> flagsRemoved) {
|
||||
+ this.maybeBroadcast(ClientboundBossEventPacket::createUpdatePropertiesPacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public @NotNull Iterable<? extends BossBarViewer> viewers() {
|
||||
+ return this.vanilla == null ? Set.of() : Collections2.transform(this.vanilla.getPlayers(), ServerPlayer::getBukkitEntity);
|
||||
+ }
|
||||
+
|
||||
+ private void maybeBroadcast(final Function<BossEvent, ClientboundBossEventPacket> fn) {
|
||||
+ if (this.vanilla != null) {
|
||||
+ this.vanilla.broadcast(fn);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/adventure/ChatDecorationProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatDecorationProcessor.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
@ -1130,56 +1220,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+ return ChatFormatting.getByHexValue(color.value());
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java b/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/adventure/VanillaBossBarListener.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+package io.papermc.paper.adventure;
|
||||
+
|
||||
+import java.util.Set;
|
||||
+import java.util.function.Consumer;
|
||||
+import java.util.function.Function;
|
||||
+
|
||||
+import net.kyori.adventure.bossbar.BossBar;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.minecraft.network.protocol.game.ClientboundBossEventPacket;
|
||||
+import net.minecraft.world.BossEvent;
|
||||
+import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
+
|
||||
+public final class VanillaBossBarListener implements BossBar.Listener {
|
||||
+ private final Consumer<Function<BossEvent, ClientboundBossEventPacket>> action;
|
||||
+
|
||||
+ public VanillaBossBarListener(final Consumer<Function<BossEvent, ClientboundBossEventPacket>> action) {
|
||||
+ this.action = action;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarNameChanged(final @NonNull BossBar bar, final @NonNull Component oldName, final @NonNull Component newName) {
|
||||
+ this.action.accept(ClientboundBossEventPacket::createUpdateNamePacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarProgressChanged(final @NonNull BossBar bar, final float oldProgress, final float newProgress) {
|
||||
+ this.action.accept(ClientboundBossEventPacket::createUpdateProgressPacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarColorChanged(final @NonNull BossBar bar, final BossBar.@NonNull Color oldColor, final BossBar.@NonNull Color newColor) {
|
||||
+ this.action.accept(ClientboundBossEventPacket::createUpdateStylePacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarOverlayChanged(final @NonNull BossBar bar, final BossBar.@NonNull Overlay oldOverlay, final BossBar.@NonNull Overlay newOverlay) {
|
||||
+ this.action.accept(ClientboundBossEventPacket::createUpdateStylePacket);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void bossBarFlagsChanged(final @NonNull BossBar bar, final @NonNull Set<BossBar.Flag> flagsAdded, final @NonNull Set<BossBar.Flag> flagsRemoved) {
|
||||
+ this.action.accept(ClientboundBossEventPacket::createUpdatePropertiesPacket);
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
@ -1206,6 +1246,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+ return net.minecraft.network.chat.Component.Serializer.fromJson(GsonComponentSerializer.gson().serializer().toJsonTree(component));
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/adventure/providers/BossBarImplementationProvider.java b/src/main/java/io/papermc/paper/adventure/providers/BossBarImplementationProvider.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/io/papermc/paper/adventure/providers/BossBarImplementationProvider.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+package io.papermc.paper.adventure.providers;
|
||||
+
|
||||
+import io.papermc.paper.adventure.BossBarImplementationImpl;
|
||||
+import net.kyori.adventure.bossbar.BossBar;
|
||||
+import net.kyori.adventure.bossbar.BossBarImplementation;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+@SuppressWarnings("UnstableApiUsage") // permitted provider
|
||||
+public class BossBarImplementationProvider implements BossBarImplementation.Provider {
|
||||
+ @Override
|
||||
+ public @NotNull BossBarImplementation create(final @NotNull BossBar bar) {
|
||||
+ return new BossBarImplementationImpl(bar);
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
@ -1561,48 +1621,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+ return builder -> builder.flattener(PaperAdventure.FLATTENER);
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java b/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/net/kyori/adventure/bossbar/HackyBossBarPlatformBridge.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+package net.kyori.adventure.bossbar;
|
||||
+
|
||||
+import io.papermc.paper.adventure.PaperAdventure;
|
||||
+import io.papermc.paper.adventure.VanillaBossBarListener;
|
||||
+import net.minecraft.server.level.ServerBossEvent;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+
|
||||
+public abstract class HackyBossBarPlatformBridge {
|
||||
+ public ServerBossEvent vanilla$bar;
|
||||
+ private VanillaBossBarListener vanilla$listener;
|
||||
+
|
||||
+ public final void paper$playerShow(final CraftPlayer player) {
|
||||
+ if (this.vanilla$bar == null) {
|
||||
+ final BossBar $this = (BossBar) this;
|
||||
+ this.vanilla$bar = new ServerBossEvent(
|
||||
+ PaperAdventure.asVanilla($this.name()),
|
||||
+ PaperAdventure.asVanilla($this.color()),
|
||||
+ PaperAdventure.asVanilla($this.overlay())
|
||||
+ );
|
||||
+ this.vanilla$bar.adventure = $this;
|
||||
+ this.vanilla$listener = new VanillaBossBarListener(this.vanilla$bar::broadcast);
|
||||
+ $this.addListener(this.vanilla$listener);
|
||||
+ }
|
||||
+ this.vanilla$bar.addPlayer(player.getHandle());
|
||||
+ }
|
||||
+
|
||||
+ public final void paper$playerHide(final CraftPlayer player) {
|
||||
+ if (this.vanilla$bar != null) {
|
||||
+ this.vanilla$bar.removePlayer(player.getHandle());
|
||||
+ if (this.vanilla$bar.getPlayers().isEmpty()) {
|
||||
+ ((BossBar) this).removeListener(this.vanilla$listener);
|
||||
+ this.vanilla$bar = null;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/net/minecraft/ChatFormatting.java b/src/main/java/net/minecraft/ChatFormatting.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/ChatFormatting.java
|
||||
@ -3992,14 +4010,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+
|
||||
+ // resetTitle implemented above
|
||||
+
|
||||
+ private @Nullable Set<net.kyori.adventure.bossbar.BossBar> activeBossBars;
|
||||
+
|
||||
+ @Override
|
||||
+ public @NotNull Iterable<? extends net.kyori.adventure.bossbar.BossBar> activeBossBars() {
|
||||
+ if (this.activeBossBars != null) {
|
||||
+ return java.util.Collections.unmodifiableSet(this.activeBossBars);
|
||||
+ }
|
||||
+ return Set.of();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void showBossBar(final net.kyori.adventure.bossbar.BossBar bar) {
|
||||
+ ((net.kyori.adventure.bossbar.HackyBossBarPlatformBridge) bar).paper$playerShow(this);
|
||||
+ net.kyori.adventure.bossbar.BossBarImplementation.get(bar, io.papermc.paper.adventure.BossBarImplementationImpl.class).playerShow(this);
|
||||
+ if (this.activeBossBars == null) {
|
||||
+ this.activeBossBars = new HashSet<>();
|
||||
+ }
|
||||
+ this.activeBossBars.add(bar);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void hideBossBar(final net.kyori.adventure.bossbar.BossBar bar) {
|
||||
+ ((net.kyori.adventure.bossbar.HackyBossBarPlatformBridge) bar).paper$playerHide(this);
|
||||
+ net.kyori.adventure.bossbar.BossBarImplementation.get(bar, io.papermc.paper.adventure.BossBarImplementationImpl.class).playerHide(this);
|
||||
+ if (this.activeBossBars != null) {
|
||||
+ this.activeBossBars.remove(bar);
|
||||
+ if (this.activeBossBars.isEmpty()) {
|
||||
+ this.activeBossBars = null;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
@ -4942,6 +4980,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
HashSet<Player> reference = new HashSet<Player>(players.size());
|
||||
for (ServerPlayer player : players) {
|
||||
reference.add(player.getBukkitEntity());
|
||||
diff --git a/src/main/resources/META-INF/services/net.kyori.adventure.bossbar.BossBarImplementation$Provider b/src/main/resources/META-INF/services/net.kyori.adventure.bossbar.BossBarImplementation$Provider
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/resources/META-INF/services/net.kyori.adventure.bossbar.BossBarImplementation$Provider
|
||||
@@ -0,0 +1 @@
|
||||
+io.papermc.paper.adventure.providers.BossBarImplementationProvider
|
||||
diff --git a/src/main/resources/META-INF/services/net.kyori.adventure.text.event.ClickCallback$Provider b/src/main/resources/META-INF/services/net.kyori.adventure.text.event.ClickCallback$Provider
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
|
Loading…
Reference in New Issue
Block a user