From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MiniDigger <admin@minidigger.me>
Date: Mon, 20 Jan 2020 21:38:15 +0100
Subject: [PATCH] Implement Player Client Options API


diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6f4400df3d8ec7e06a996de54f8cabba57885e1
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java
@@ -0,0 +1,74 @@
+package com.destroystokyo.paper;
+
+import com.google.common.base.Objects;
+
+import java.util.StringJoiner;
+
+public class PaperSkinParts implements SkinParts {
+
+    private final int raw;
+
+    public PaperSkinParts(int raw) {
+        this.raw = raw;
+    }
+
+    public boolean hasCapeEnabled() {
+        return (raw & 1) == 1;
+    }
+
+    public boolean hasJacketEnabled() {
+        return (raw >> 1 & 1) == 1;
+    }
+
+    public boolean hasLeftSleeveEnabled() {
+        return (raw >> 2 & 1) == 1;
+    }
+
+    public boolean hasRightSleeveEnabled() {
+        return (raw >> 3 & 1) == 1;
+    }
+
+    public boolean hasLeftPantsEnabled() {
+        return (raw >> 4 & 1) == 1;
+    }
+
+    public boolean hasRightPantsEnabled() {
+        return (raw >> 5 & 1) == 1;
+    }
+
+    public boolean hasHatsEnabled() {
+        return (raw >> 6 & 1) == 1;
+    }
+
+    @Override
+    public int getRaw() {
+        return raw;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PaperSkinParts that = (PaperSkinParts) o;
+        return raw == that.raw;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(raw);
+    }
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]")
+            .add("raw=" + raw)
+            .add("cape=" + hasCapeEnabled())
+            .add("jacket=" + hasJacketEnabled())
+            .add("leftSleeve=" + hasLeftSleeveEnabled())
+            .add("rightSleeve=" + hasRightSleeveEnabled())
+            .add("leftPants=" + hasLeftPantsEnabled())
+            .add("rightPants=" + hasRightPantsEnabled())
+            .add("hats=" + hasHatsEnabled())
+            .toString();
+    }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 1f62b23c69053e30c8a0d2989eb4a1d5976f0f13..4ab83e544dbe2b4fdd351ac30359089df847dc81 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -1807,6 +1807,7 @@ public class ServerPlayer extends Player {
     public String locale = null; // CraftBukkit - add, lowercase // Paper - default to null
     public java.util.Locale adventure$locale = java.util.Locale.US; // Paper
     public void updateOptions(ServerboundClientInformationPacket packet) {
+        new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), packet.language, packet.viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(packet.getChatVisibility().name()), packet.getChatColors(), new com.destroystokyo.paper.PaperSkinParts(packet.getModelCustomisation()), packet.getMainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT).callEvent(); // Paper - settings event
         // CraftBukkit start
         if (getMainArm() != packet.getMainHand()) {
             PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index a30010595f98de56c59d54a97e5a5d0f0ab28d2d..80914eed36523b120e4d056497cb2ccb1cb5110d 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -521,6 +521,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
     public void setViewDistance(int viewDistance) {
         throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
     }
+
+    @Override
+    public <T> T getClientOption(com.destroystokyo.paper.ClientOption<T> type) {
+        if(com.destroystokyo.paper.ClientOption.SKIN_PARTS.equals(type)) {
+            return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION)));
+        } else if(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED.equals(type)) {
+            return type.getType().cast(getHandle().canChatInColor());
+        } else if(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY.equals(type)) {
+            return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name()));
+        } else if(com.destroystokyo.paper.ClientOption.LOCALE.equals(type)) {
+            return type.getType().cast(getLocale());
+        } else if(com.destroystokyo.paper.ClientOption.MAIN_HAND.equals(type)) {
+            return type.getType().cast(getMainHand());
+        } else if(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE.equals(type)) {
+            return type.getType().cast(getClientViewDistance());
+        }
+        throw new RuntimeException("Unknown settings type");
+    }
     // Paper end
 
     @Override
diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cd015dc5a2e012ac827c2b2d9aa5542b0591afb
--- /dev/null
+++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java
@@ -0,0 +1,19 @@
+package io.papermc.paper.world;
+
+import com.destroystokyo.paper.ClientOption;
+import net.minecraft.network.chat.TranslatableComponent;
+import net.minecraft.world.entity.player.ChatVisiblity;
+import org.bukkit.Difficulty;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TranslationKeyTest {
+
+    @Test
+    public void testChatVisibilityKeys() {
+        for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) {
+            if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue;
+            Assert.assertEquals(chatVisibility + "'s translation key doesn't match", ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey());
+        }
+    }
+}