From cb940e72b1d8ac41ef38c0f9cf935e66ab905c6f Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Tue, 26 Nov 2024 18:46:12 +0100
Subject: [PATCH] Expose particle status client option (#11573)

---
 .../api/Add-Player-Client-Options-API.patch   | 41 +++++++++++--
 .../Add-methods-to-get-translation-keys.patch |  9 +--
 .../Implement-Player-Client-Options-API.patch | 60 ++++++++++---------
 3 files changed, 72 insertions(+), 38 deletions(-)

diff --git a/patches/api/Add-Player-Client-Options-API.patch b/patches/api/Add-Player-Client-Options-API.patch
index d022138b4d..7f3518c2aa 100644
--- a/patches/api/Add-Player-Client-Options-API.patch
+++ b/patches/api/Add-Player-Client-Options-API.patch
@@ -26,8 +26,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static final ClientOption<String> LOCALE = new ClientOption<>(String.class);
 +    public static final ClientOption<MainHand> MAIN_HAND = new ClientOption<>(MainHand.class);
 +    public static final ClientOption<Integer> VIEW_DISTANCE = new ClientOption<>(Integer.class);
-+    public static final ClientOption<Boolean> ALLOW_SERVER_LISTINGS = new ClientOption<>(Boolean.class);
 +    public static final ClientOption<Boolean> TEXT_FILTERING_ENABLED = new ClientOption<>(Boolean.class);
++    public static final ClientOption<Boolean> ALLOW_SERVER_LISTINGS = new ClientOption<>(Boolean.class);
++    public static final ClientOption<ParticleVisibility> PARTICLE_VISIBILITY = new ClientOption<>(ParticleVisibility.class);
 +
 +    private final Class<T> type;
 +
@@ -60,6 +61,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return "options.chat.visibility." + this.name;
 +        }
 +    }
++
++    public enum ParticleVisibility implements Translatable {
++        ALL("all"),
++        DECREASED("decreased"),
++        MINIMAL("minimal");
++
++        public static final Index<String, ParticleVisibility> NAMES = Index.create(ParticleVisibility.class, particleVisibility -> particleVisibility.name);
++        private final String name;
++
++        ParticleVisibility(final String name) {
++            this.name = name;
++        }
++
++        @Override
++        public String translationKey() {
++            return "options.particles." + this.name;
++        }
++    }
 +}
 diff --git a/src/main/java/com/destroystokyo/paper/SkinParts.java b/src/main/java/com/destroystokyo/paper/SkinParts.java
 new file mode 100644
@@ -97,6 +116,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +import com.destroystokyo.paper.ClientOption;
 +import com.destroystokyo.paper.ClientOption.ChatVisibility;
++import com.destroystokyo.paper.ClientOption.ParticleVisibility;
 +import com.destroystokyo.paper.SkinParts;
 +import java.util.Map;
 +import org.bukkit.entity.Player;
@@ -122,6 +142,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private final MainHand mainHand;
 +    private final boolean allowsServerListings;
 +    private final boolean textFilteringEnabled;
++    private final ParticleVisibility particleVisibility;
 +
 +    @Deprecated
 +    public PlayerClientOptionsChangeEvent(final Player player, final String locale, final int viewDistance, final ChatVisibility chatVisibility, final boolean chatColors, final SkinParts skinParts, final MainHand mainHand) {
@@ -134,6 +155,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.mainHand = mainHand;
 +        this.allowsServerListings = false;
 +        this.textFilteringEnabled = false;
++        this.particleVisibility = ParticleVisibility.ALL;
 +    }
 +
 +    @ApiStatus.Internal
@@ -148,6 +170,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.mainHand = (MainHand) options.get(ClientOption.MAIN_HAND);
 +        this.allowsServerListings = (boolean) options.get(ClientOption.ALLOW_SERVER_LISTINGS);
 +        this.textFilteringEnabled = (boolean) options.get(ClientOption.TEXT_FILTERING_ENABLED);
++        this.particleVisibility = (ParticleVisibility) options.get(ClientOption.PARTICLE_VISIBILITY);
 +    }
 +
 +    public String getLocale() {
@@ -198,6 +221,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return this.mainHand != this.player.getClientOption(ClientOption.MAIN_HAND);
 +    }
 +
++    public boolean hasTextFilteringEnabled() {
++        return this.textFilteringEnabled;
++    }
++
++    public boolean hasTextFilteringChanged() {
++        return this.textFilteringEnabled != this.player.getClientOption(ClientOption.TEXT_FILTERING_ENABLED);
++    }
++
 +    public boolean allowsServerListings() {
 +        return this.allowsServerListings;
 +    }
@@ -206,12 +237,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return this.allowsServerListings != this.player.getClientOption(ClientOption.ALLOW_SERVER_LISTINGS);
 +    }
 +
-+    public boolean hasTextFilteringEnabled() {
-+        return this.textFilteringEnabled;
++    public ParticleVisibility getParticleVisibility() {
++        return this.particleVisibility;
 +    }
 +
-+    public boolean hasTextFilteringChanged() {
-+        return this.textFilteringEnabled != this.player.getClientOption(ClientOption.TEXT_FILTERING_ENABLED);
++    public boolean hasParticleVisibilityChanged() {
++        return this.particleVisibility != this.player.getClientOption(ClientOption.PARTICLE_VISIBILITY);
 +    }
 +
 +    @Override
diff --git a/patches/server/Add-methods-to-get-translation-keys.patch b/patches/server/Add-methods-to-get-translation-keys.patch
index 28bd0adfaf..bbee336f52 100644
--- a/patches/server/Add-methods-to-get-translation-keys.patch
+++ b/patches/server/Add-methods-to-get-translation-keys.patch
@@ -139,23 +139,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import com.destroystokyo.paper.ClientOption;
 +import java.util.Locale;
 +import java.util.Map;
-+import net.minecraft.core.registries.Registries;
 +import net.minecraft.network.chat.contents.TranslatableContents;
 +import net.minecraft.resources.ResourceKey;
-+import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.server.level.ParticleStatus;
  import net.minecraft.world.entity.player.ChatVisiblity;
 +import net.minecraft.world.flag.FeatureFlags;
 +import net.minecraft.world.level.GameRules;
 +import net.minecraft.world.level.GameType;
 +import net.minecraft.world.level.biome.Biome;
- import org.bukkit.Difficulty;
++import org.bukkit.Difficulty;
 +import org.bukkit.FireworkEffect;
 +import org.bukkit.GameMode;
 +import org.bukkit.GameRule;
-+import org.bukkit.MusicInstrument;
 +import org.bukkit.attribute.Attribute;
 +import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
 +import org.bukkit.support.RegistryHelper;
 +import org.bukkit.support.environment.AllFeatures;
  import org.junit.jupiter.api.Assertions;
@@ -167,7 +164,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Test
 @@ -0,0 +0,0 @@ public class TranslationKeyTest {
-             Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match");
+             Assertions.assertEquals(ParticleStatus.valueOf(particleVisibility.name()).getKey(), particleVisibility.translationKey(), particleVisibility + "'s translation key doesn't match");
          }
      }
 +
diff --git a/patches/server/Implement-Player-Client-Options-API.patch b/patches/server/Implement-Player-Client-Options-API.patch
index 9f6c0e5ad8..94cf30a03c 100644
--- a/patches/server/Implement-Player-Client-Options-API.patch
+++ b/patches/server/Implement-Player-Client-Options-API.patch
@@ -5,6 +5,7 @@ Subject: [PATCH] Implement Player Client Options API
 
 == AT ==
 public net.minecraft.world.entity.player.Player DATA_PLAYER_MODE_CUSTOMISATION
+public net.minecraft.server.level.ServerPlayer particleStatus
 
 diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
 new file mode 100644
@@ -100,26 +101,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          // CraftBukkit start
 @@ -0,0 +0,0 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
-         }
      }
  
-+    // Paper start - Client option API
-+    private java.util.Map<com.destroystokyo.paper.ClientOption<?>, ?> getClientOptionMap(String locale, int viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility chatVisibility, boolean chatColors, com.destroystokyo.paper.PaperSkinParts skinParts, org.bukkit.inventory.MainHand mainHand, boolean allowsServerListing, boolean textFilteringEnabled) {
-+        java.util.Map<com.destroystokyo.paper.ClientOption<?>, Object> map = new java.util.HashMap<>();
-+        map.put(com.destroystokyo.paper.ClientOption.LOCALE, locale);
-+        map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, viewDistance);
-+        map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, chatVisibility);
-+        map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, chatColors);
-+        map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, skinParts);
-+        map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, mainHand);
-+        map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, allowsServerListing);
-+        map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, textFilteringEnabled);
-+        return map;
-+    }
-+    // Paper end
-+
      public void updateOptions(ClientInformation clientOptions) {
-+        new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), getClientOptionMap(clientOptions.language(), clientOptions.viewDistance(), com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name()), clientOptions.chatColors(), new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation()), clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT, clientOptions.allowsListing(), clientOptions.textFilteringEnabled())).callEvent(); // Paper - settings event
++        // Paper start - settings event
++        new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(this.getBukkitEntity(), Util.make(new java.util.IdentityHashMap<>(), map -> {
++            map.put(com.destroystokyo.paper.ClientOption.LOCALE, clientOptions.language());
++            map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, clientOptions.viewDistance());
++            map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name()));
++            map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, clientOptions.chatColors());
++            map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation()));
++            map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
++            map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, clientOptions.textFilteringEnabled());
++            map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, clientOptions.allowsListing());
++            map.put(com.destroystokyo.paper.ClientOption.PARTICLE_VISIBILITY, com.destroystokyo.paper.ClientOption.ParticleVisibility.valueOf(clientOptions.particleStatus().name()));
++        })).callEvent();
++        // Paper end - settings event
          // CraftBukkit start
          if (this.getMainArm() != clientOptions.mainHand()) {
              PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
@@ -147,21 +144,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    @Override
 +    public <T> T getClientOption(com.destroystokyo.paper.ClientOption<T> type) {
 +        if (com.destroystokyo.paper.ClientOption.SKIN_PARTS == type) {
-+            return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION)));
++            return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(this.getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION)));
 +        } else if (com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED == type) {
-+            return type.getType().cast(getHandle().canChatInColor());
++            return type.getType().cast(this.getHandle().canChatInColor());
 +        } else if (com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY == type) {
-+            return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name()));
++            return type.getType().cast(this.getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(this.getHandle().getChatVisibility().name()));
 +        } else if (com.destroystokyo.paper.ClientOption.LOCALE == type) {
-+            return type.getType().cast(getLocale());
++            return type.getType().cast(this.getLocale());
 +        } else if (com.destroystokyo.paper.ClientOption.MAIN_HAND == type) {
-+            return type.getType().cast(getMainHand());
++            return type.getType().cast(this.getMainHand());
 +        } else if (com.destroystokyo.paper.ClientOption.VIEW_DISTANCE == type) {
-+            return type.getType().cast(getClientViewDistance());
-+        } else if (com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS == type) {
-+            return type.getType().cast(getHandle().allowsListing());
++            return type.getType().cast(this.getClientViewDistance());
 +        } else if (com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED == type) {
-+            return type.getType().cast(getHandle().isTextFilteringEnabled());
++            return type.getType().cast(this.getHandle().isTextFilteringEnabled());
++        } else if (com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS == type) {
++            return type.getType().cast(this.getHandle().allowsListing());
++        } else if (com.destroystokyo.paper.ClientOption.PARTICLE_VISIBILITY == type) {
++            return type.getType().cast(com.destroystokyo.paper.ClientOption.ParticleVisibility.valueOf(this.getHandle().particleStatus.name()));
 +        }
 +        throw new RuntimeException("Unknown settings type");
 +    }
@@ -177,8 +176,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package io.papermc.paper.world;
 +
 +import com.destroystokyo.paper.ClientOption;
++import net.minecraft.server.level.ParticleStatus;
 +import net.minecraft.world.entity.player.ChatVisiblity;
-+import org.bukkit.Difficulty;
 +import org.junit.jupiter.api.Assertions;
 +import org.junit.jupiter.api.Test;
 +
@@ -191,4 +190,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match");
 +        }
 +    }
++
++    @Test
++    public void testParticleVisibilityKeys() {
++        for (ClientOption.ParticleVisibility particleVisibility : ClientOption.ParticleVisibility.values()) {
++            Assertions.assertEquals(ParticleStatus.valueOf(particleVisibility.name()).getKey(), particleVisibility.translationKey(), particleVisibility + "'s translation key doesn't match");
++        }
++    }
 +}