├── src └── main │ └── java │ └── dev │ └── fls │ └── tablist │ ├── page │ ├── PartType.java │ ├── parts │ │ ├── body │ │ │ ├── lines │ │ │ │ ├── LineType.java │ │ │ │ └── BodyLine.java │ │ │ └── Body.java │ │ ├── Footer.java │ │ └── Header.java │ ├── PagePart.java │ └── OptionalPart.java │ ├── utils │ ├── PacketUtils.java │ └── mojangapi │ │ ├── MinecraftProfile.java │ │ └── MinecraftProfileProperty.java │ ├── animations │ ├── tasks │ │ ├── TablistPingTask.java │ │ └── TablistAnimationTask.java │ └── TablistAnimation.java │ ├── TabListTemplate.java │ └── skin │ └── SkinColor.java ├── .gitignore ├── pom.xml └── README.md /src/main/java/dev/fls/tablist/page/PartType.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page; 2 | 3 | public enum PartType { 4 | 5 | HEADER, 6 | FOOTER, 7 | BODY; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/page/parts/body/lines/LineType.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page.parts.body.lines; 2 | 3 | public enum LineType { 4 | 5 | PLAYER, 6 | BLANK; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/page/PagePart.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page; 2 | 3 | public abstract class PagePart { 4 | 5 | private final PartType type; 6 | 7 | public PagePart(PartType type) { 8 | this.type = type; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/page/parts/Footer.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page.parts; 2 | 3 | import dev.fls.tablist.page.OptionalPart; 4 | import dev.fls.tablist.page.PartType; 5 | 6 | public class Footer extends OptionalPart { 7 | 8 | public Footer() { 9 | super(PartType.FOOTER); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/page/parts/Header.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page.parts; 2 | 3 | import dev.fls.tablist.page.OptionalPart; 4 | import dev.fls.tablist.page.PartType; 5 | 6 | public class Header extends OptionalPart { 7 | 8 | public Header() { 9 | super(PartType.HEADER); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/utils/PacketUtils.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.utils; 2 | 3 | import net.minecraft.server.v1_8_R3.Packet; 4 | import net.minecraft.server.v1_8_R3.PlayerConnection; 5 | import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; 6 | import org.bukkit.entity.Player; 7 | 8 | public class PacketUtils { 9 | 10 | public static void sendPacket(Player player, Packet packet) { 11 | PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection; 12 | connection.sendPacket(packet); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/utils/mojangapi/MinecraftProfile.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.utils.mojangapi; 2 | 3 | /** 4 | * This class is a copy of Mojang's API MinecraftProfile JSON file on 5 | * https://sessionserver.mojang.com/session/minecraft/profile/uuid-that-you-want?unsigned=false 6 | * 7 | * @author _FearLessS 8 | * @since v1.0 (2021) 9 | */ 10 | public class MinecraftProfile { 11 | 12 | private final String id; 13 | private final String name; 14 | private final MinecraftProfileProperty[] properties; 15 | 16 | public MinecraftProfile(String id, String name, MinecraftProfileProperty[] properties) { 17 | this.id = id; 18 | this.name = name; 19 | this.properties = properties; 20 | } 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public MinecraftProfileProperty[] getProperties() { 31 | return properties; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "MinecraftProfile{" + 37 | "id='" + id + '\'' + 38 | ", name='" + name + '\'' + 39 | ", properties=" + properties.toString() + 40 | '}'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/animations/tasks/TablistPingTask.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.animations.tasks; 2 | 3 | import dev.fls.tablist.TabListTemplate; 4 | import dev.fls.tablist.page.parts.body.lines.BodyLine; 5 | import dev.fls.tablist.page.parts.body.lines.LineType; 6 | import lombok.Getter; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.scheduler.BukkitRunnable; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | /** 17 | * This task update every custom lines ping 18 | * 19 | */ 20 | @Getter 21 | public class TablistPingTask extends BukkitRunnable { 22 | 23 | private final List templates = new ArrayList<>(); 24 | 25 | @Override 26 | public void run() { 27 | for(TabListTemplate template : templates) { 28 | 29 | List lines = template.getBody().linesAsList(); 30 | 31 | for(UUID uuid : template.getDisplayedTo()) { 32 | Player player = Bukkit.getPlayer(uuid); 33 | for(BodyLine line : lines) { 34 | if(!line.getLineType().equals(LineType.PLAYER)) continue; 35 | line.updatePing(player); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/utils/mojangapi/MinecraftProfileProperty.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.utils.mojangapi; 2 | 3 | /** 4 | * This class is a copy of Mojang's API MinecraftProfile JSON file on 5 | * https://sessionserver.mojang.com/session/minecraft/profile/uuid-that-you-want?unsigned=false 6 | * 7 | * More precisely the profile properties array 8 | * 9 | * @author _FearLessS 10 | * @since v1.0 (2021) 11 | */ 12 | public class MinecraftProfileProperty { 13 | 14 | private final String name; 15 | private final String value; 16 | private final String signature; 17 | 18 | public MinecraftProfileProperty(String name, String value, String signature) { 19 | this.name = name; 20 | this.value = value; 21 | this.signature = signature; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public String getValue() { 29 | return value; 30 | } 31 | 32 | public String getSignature() { 33 | return signature; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "MinecraftProfileProperty{" + 39 | "name='" + name + '\'' + 40 | ", value='" + value + '\'' + 41 | ", signature='" + signature + '\'' + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/TabListTemplate.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist; 2 | 3 | import dev.fls.tablist.page.OptionalPart; 4 | import dev.fls.tablist.page.parts.Footer; 5 | import dev.fls.tablist.page.parts.Header; 6 | import dev.fls.tablist.page.parts.body.Body; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.plugin.java.JavaPlugin; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | @Getter 17 | @Setter 18 | public class TabListTemplate { 19 | 20 | private static JavaPlugin plugin; 21 | 22 | public TabListTemplate(JavaPlugin pl) { 23 | plugin = pl; 24 | } 25 | 26 | private final Header header = new Header(); 27 | private final Footer footer = new Footer(); 28 | private Body body = new Body(); 29 | private final List displayedTo = new ArrayList<>(); 30 | 31 | public static JavaPlugin getPlugin() { 32 | return plugin; 33 | } 34 | 35 | public void show(Player player) { 36 | displayedTo.add(player.getUniqueId()); 37 | OptionalPart.sendPacket(player, header, footer); 38 | body.show(player); 39 | } 40 | 41 | public void hide(Player player) { 42 | if(displayedTo.contains(player.getUniqueId())) displayedTo.remove(player.getUniqueId()); 43 | OptionalPart.sendPacket(player, new Header(), new Footer()); 44 | body.reset(player); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | 11 | # Compiled class file 12 | *.class 13 | 14 | # Log file 15 | *.log 16 | 17 | # BlueJ files 18 | *.ctxt 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | 32 | *~ 33 | 34 | # temporary files which can be created if a process still has a handle open of a deleted file 35 | .fuse_hidden* 36 | 37 | # KDE directory preferences 38 | .directory 39 | 40 | # Linux trash folder which might appear on any partition or disk 41 | .Trash-* 42 | 43 | # .nfs files are created when an open file is removed but is still being accessed 44 | .nfs* 45 | 46 | # General 47 | .DS_Store 48 | .AppleDouble 49 | .LSOverride 50 | 51 | # Icon must end with two \r 52 | Icon 53 | 54 | # Thumbnails 55 | ._* 56 | 57 | # Files that might appear in the root of a volume 58 | .DocumentRevisions-V100 59 | .fseventsd 60 | .Spotlight-V100 61 | .TemporaryItems 62 | .Trashes 63 | .VolumeIcon.icns 64 | .com.apple.timemachine.donotpresent 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | 73 | # Windows thumbnail cache files 74 | Thumbs.db 75 | Thumbs.db:encryptable 76 | ehthumbs.db 77 | ehthumbs_vista.db 78 | 79 | # Dump file 80 | *.stackdump 81 | 82 | # Folder config file 83 | [Dd]esktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msix 92 | *.msm 93 | *.msp 94 | 95 | # Windows shortcuts 96 | *.lnk 97 | 98 | target/ 99 | 100 | pom.xml.tag 101 | pom.xml.releaseBackup 102 | pom.xml.versionsBackup 103 | pom.xml.next 104 | 105 | release.properties 106 | dependency-reduced-pom.xml 107 | buildNumber.properties 108 | .mvn/timing.properties 109 | .mvn/wrapper/maven-wrapper.jar 110 | .flattened-pom.xml 111 | 112 | # Common working directory 113 | run/ 114 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/animations/TablistAnimation.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.animations; 2 | 3 | import dev.fls.tablist.TabListTemplate; 4 | import dev.fls.tablist.animations.tasks.TablistAnimationTask; 5 | import dev.fls.tablist.page.parts.body.Body; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import org.bukkit.plugin.java.JavaPlugin; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Setter 14 | @Getter 15 | public class TablistAnimation { 16 | 17 | private final JavaPlugin plugin; 18 | 19 | private boolean animateHeader, animateFooter, animateBody; 20 | private final List headerFrames = new ArrayList<>(), footerFrames = new ArrayList<>(); 21 | private final List bodyFrames = new ArrayList<>(); 22 | private final int headerInterval, footerInterval, bodyInterval, headerAnimationDelay, bodyAnimationDelay, footerAnimationDelay; 23 | 24 | private boolean isRunning; 25 | private final TabListTemplate template; 26 | private TablistAnimationTask task; 27 | 28 | 29 | public TablistAnimation(JavaPlugin plugin, int headerInterval, int footerInterval, int bodyInterval, int headerAnimationDelay, int bodyAnimationDelay, int footerAnimationDelay) { 30 | this.headerInterval = headerInterval; 31 | this.footerInterval = footerInterval; 32 | this.bodyInterval = bodyInterval; 33 | this.plugin = plugin; 34 | this.template = new TabListTemplate(plugin); 35 | this.headerAnimationDelay = headerAnimationDelay; 36 | this.bodyAnimationDelay = bodyAnimationDelay; 37 | this.footerAnimationDelay = footerAnimationDelay; 38 | 39 | if(headerInterval != -1) animateHeader = true; 40 | if(bodyInterval != -1) animateBody = true; 41 | if(footerInterval != -1) animateFooter = true; 42 | } 43 | 44 | public TablistAnimation addBodyFrame(Body body) { 45 | bodyFrames.add(body); 46 | return this; 47 | } 48 | 49 | public TablistAnimation addHeaderFrame(String... lines) { 50 | headerFrames.add(lines); 51 | return this; 52 | } 53 | 54 | public TablistAnimation addFooterFrame(String... lines) { 55 | footerFrames.add(lines); 56 | return this; 57 | } 58 | 59 | public void run() { 60 | if(isRunning) return; 61 | task = new TablistAnimationTask(this); 62 | task.runTaskTimerAsynchronously(plugin,0,1); 63 | } 64 | 65 | public void stop() { 66 | if(task == null) return; 67 | task.cancel(); 68 | isRunning = false; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | dev.fls 8 | TabListAPI 9 | 1.0-SNAPSHOT 10 | jar 11 | 12 | TabListAPI 13 | 14 | 15 | 1.8 16 | UTF-8 17 | 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-compiler-plugin 24 | 3.8.1 25 | 26 | 9 27 | 9 28 | 29 | 30 | 31 | org.apache.maven.plugins 32 | maven-shade-plugin 33 | 3.2.4 34 | 35 | 36 | package 37 | 38 | shade 39 | 40 | 41 | false 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | src/main/resources 50 | true 51 | 52 | 53 | 54 | 55 | 56 | 57 | spigotmc-repo 58 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 59 | 60 | 61 | sonatype 62 | https://oss.sonatype.org/content/groups/public/ 63 | 64 | 65 | 66 | 67 | 68 | org.spigotmc 69 | spigot-api 70 | 1.17.1-R0.1-SNAPSHOT 71 | provided 72 | 73 | 74 | org.projectlombok 75 | lombok 76 | RELEASE 77 | compile 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/page/OptionalPart.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page; 2 | 3 | import dev.fls.tablist.page.parts.Footer; 4 | import dev.fls.tablist.page.parts.Header; 5 | import dev.fls.tablist.utils.PacketUtils; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import net.minecraft.server.v1_8_R3.IChatBaseComponent; 9 | import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerListHeaderFooter; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.lang.reflect.Field; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | @Getter 18 | @Setter 19 | public abstract class OptionalPart extends PagePart { 20 | 21 | public OptionalPart(PartType type) { 22 | super(type); 23 | } 24 | 25 | private String[] lines = new String[]{}; 26 | 27 | public static void sendPacket(Player player, Header header, Footer footer) { 28 | PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(IChatBaseComponent.ChatSerializer.a(header.getHeaderText())); 29 | 30 | try { 31 | Field headerField = packet.getClass().getDeclaredField("b"); 32 | headerField.setAccessible(true); 33 | headerField.set(packet, IChatBaseComponent.ChatSerializer.a(footer.getHeaderText())); 34 | headerField.setAccessible(false); 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } 38 | 39 | PacketUtils.sendPacket(player, packet); 40 | } 41 | 42 | public void addLine(String line) { 43 | List lines = new ArrayList<>(Arrays.asList(this.lines)); 44 | lines.add(line); 45 | this.lines = lines.toArray(new String[0]); 46 | } 47 | 48 | public void removeLine(int index) { 49 | List lines = new ArrayList<>(Arrays.asList(this.lines)); 50 | lines.remove(index); 51 | this.lines = lines.toArray(new String[0]); 52 | } 53 | 54 | public void addLines(String... added) { 55 | List lines = new ArrayList<>(Arrays.asList(this.lines)); 56 | lines.addAll(Arrays.asList(added)); 57 | this.lines = lines.toArray(new String[0]); 58 | } 59 | 60 | public void removeLines(int... removed) { 61 | List lines = new ArrayList<>(Arrays.asList(this.lines)); 62 | lines.removeAll(Arrays.asList(removed)); 63 | this.lines = lines.toArray(new String[0]); 64 | } 65 | 66 | 67 | /** 68 | * This method returns all header lines as a single JSON String 69 | * By this way, it could be sended in a packet 70 | * 71 | * @return 72 | */ 73 | public String getHeaderText() { 74 | String text = "{\"text\": \""; 75 | List linesList = Arrays.asList(lines); 76 | 77 | for(String line : linesList) { 78 | int index = linesList.indexOf(line); 79 | String lineTxt = line; 80 | if(index < linesList.size() - 1) lineTxt += "\n"; 81 | text += lineTxt; 82 | } 83 | 84 | text += "\"}"; 85 | return text; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | lyra-logo 4 |

5 | 6 | 7 | 8 |

9 |

🦔 SonicTab - Create a custom TabList faster than Sonic.

10 | SonicTab is an API (Application Programming Interface) that will allow you to create customized TabList(s) easily and fastly. By offering you a simple to understand 2-Dimensional Grid system. 11 |

12 | 13 | 14 | 15 | 16 | ## 🏷️ Features 17 | 18 | - 2D Grid system 19 | - Multi-Line header and footer 20 | - Fullscreen mode 21 | - Cross platform 22 | 23 | 24 | ## 📂 Installation 25 | 26 | Installation of SonicTab 27 | 28 | ```bash 29 | git clone https://github.com/rootxls/SonicTab 30 | ``` 31 | 32 | Now you can open this folder with your IDE. 33 | 34 | ## 📐 Usage 35 | 36 | 37 | **Template** 38 | 39 | First, you'll have to create a new TabList template, this will contain all your TabList properties. 40 | ```java 41 | private final TabListTemplate template = new TabListTemplate(); 42 | ``` 43 | 44 | **Header / Footer** 45 | 46 | Secondly, you can add Header & Footer, thoses features allows you to put text on top and bottom of the tablist. 47 | 48 | ```java 49 | // HEADER 50 | template.getHeader().addLines("Line 1", "Line 2"); 51 | 52 | // FOOTER 53 | template.getFooter().addLines("Line 1", "Line 2"); 54 | ``` 55 | 56 | **Body Customization** 57 | 58 | Afterwards, you can add a Body. Originally this area contains the list of players. But with SonicTab you can modify it to add whatever you want. To get this part of your template, use: 59 | 60 | ```java 61 | template.getBody(); 62 | ``` 63 | 64 | | Variable name | Min. value | Max. value | 65 | |---|---|---| 66 | | Columns | 1 | 4 | 67 | | X | 0 | 3 | 68 | | Y | 0 | 19 | 69 | | lineWidth | 0 | 48 | 70 | 71 | Such as:
72 | Columns: Define the number of columns you want for your tablist: 73 | 74 | ```java 75 | template.getBody().setColumns(3); 76 | ``` 77 | 78 | RemoveBaseLines: Removing base players: 79 | 80 | ```java 81 | template.getBody().setRemoveBaseLines(true); 82 | ``` 83 | Custom Lines: Add or delete your own lines into the tablist body: 84 | 85 | ```java 86 | // ADD 87 | int ping = 0; 88 | int x, z = 0; 89 | template.getBody().addLine(new BodyLine("YOUR LINE", ping, x, z)); 90 | 91 | // REMOVE 92 | template.getBody().removeLine(x, z); // THE LINE AT COORDINATES X & Z WILL BE DELETED AND REPLACED BY A BLANK LINE 93 | ``` 94 | 95 | With lines you can modify displayed head too: 96 | 97 | ```java 98 | BodyLine line = new BodyLine("YOUR LINE", ping, x, z); 99 | 100 | // TEXTURE & SIGNATURE CAN BE OPTAINED BY UPLOADING SKIN ON https://mineskin.org/ 101 | String texture = ""; 102 | String signature = ""; 103 | 104 | line.setSkin(texture, signature); 105 | ``` 106 | 107 | ## ❓ FAQ 108 | 109 | #### From which version can be used this API ? 110 | 111 | From 1.8 to 1.17 112 | 113 | 114 | ## 🤝 Contributing 115 | 116 | Contributions are always welcome! 117 | 118 | See `contributing.md` for ways to get started. 119 | 120 | Please adhere to this project's `code of conduct`. 121 | 122 | 123 | ## 📎 Related 124 | 125 | Here are the projects for which this API has been developed: 126 | 127 | - [LyraMC](https://github.com/LyraMC) 128 | 129 | 130 | ## 🔗 Links 131 | [![portfolio](https://img.shields.io/badge/my_portfolio-000?style=for-the-badge&logo=ko-fi&logoColor=white)](https://bsnk.tk/) 132 | [![linkedin](https://img.shields.io/badge/linkedin-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/bastien-siniak/) 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/animations/tasks/TablistAnimationTask.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.animations.tasks; 2 | 3 | import dev.fls.tablist.animations.TablistAnimation; 4 | import dev.fls.tablist.page.OptionalPart; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | 8 | public class TablistAnimationTask extends BukkitRunnable { 9 | 10 | private final TablistAnimation animation; 11 | 12 | public TablistAnimationTask(TablistAnimation animation) { 13 | this.animation = animation; 14 | } 15 | 16 | private int currentHeaderPause, currentFooterPause, currentBodyPause; 17 | private int currentHeaderDelay, currentFooterDelay, currentBodyDelay; 18 | private int currentHeader, currentFooter, currentBody; 19 | 20 | @Override 21 | public void run() { 22 | animation.setRunning(true); 23 | 24 | // TODO actualiser qu'une fois header et footer ou bien actualiser indépendement header et footer 25 | 26 | if (currentHeaderDelay != -1) currentHeaderDelay++; 27 | if (currentBodyDelay != -1) currentBodyDelay++; 28 | if (currentFooterDelay != -1) currentFooterDelay++; 29 | 30 | header: { 31 | if (animation.isAnimateHeader()) { 32 | if (currentHeaderDelay == animation.getHeaderInterval()) { 33 | if(currentHeader < 0) break header; 34 | animation.getTemplate().getHeader().setLines(animation.getHeaderFrames().get(currentHeader)); 35 | Bukkit.getOnlinePlayers().forEach(player -> OptionalPart.sendPacket(player, animation.getTemplate().getHeader(), animation.getTemplate().getFooter())); 36 | currentHeader = applyCurrent(currentHeader, animation.getHeaderFrames().size()); 37 | currentHeaderDelay = 0; 38 | } else { 39 | if(currentHeader == -1) { 40 | if(currentHeaderPause >= animation.getHeaderAnimationDelay()) { 41 | currentHeaderPause = 0; 42 | currentHeaderDelay = 0; 43 | currentHeader = 0; 44 | } else { 45 | currentHeaderPause++; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | body: { 53 | if (animation.isAnimateBody()) { 54 | if (currentBodyDelay == animation.getBodyInterval()) { 55 | if(currentBody < 0) break body; 56 | animation.getTemplate().setBody(animation.getBodyFrames().get(currentBody)); 57 | Bukkit.getOnlinePlayers().forEach(player -> animation.getTemplate().getBody().show(player)); 58 | currentBody = applyCurrent(currentBody, animation.getBodyFrames().size()); 59 | currentBodyDelay = 0; 60 | } else { 61 | if(currentBody == -1) { 62 | if(currentBodyPause >= animation.getBodyAnimationDelay()) { 63 | currentBodyPause = 0; 64 | currentBodyDelay = 0; 65 | currentBody = 0; 66 | } else { 67 | currentBodyPause++; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | footer: { 75 | if (animation.isAnimateFooter()) { 76 | if (currentFooterDelay == animation.getFooterInterval()) { 77 | if(currentFooter < 0) break footer; 78 | animation.getTemplate().getFooter().setLines(animation.getFooterFrames().get(currentFooter)); 79 | Bukkit.getOnlinePlayers().forEach(player -> OptionalPart.sendPacket(player, animation.getTemplate().getHeader(), animation.getTemplate().getFooter())); 80 | currentFooter = applyCurrent(currentFooter, animation.getFooterFrames().size()); 81 | currentFooterDelay = 0; 82 | } else { 83 | if(currentFooter == -1) { 84 | if(currentFooterPause >= animation.getBodyAnimationDelay()) { 85 | currentFooterPause = 0; 86 | currentFooterDelay = 0; 87 | currentFooter = 0; 88 | } else { 89 | currentFooterPause++; 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | @Override 98 | public synchronized void cancel() throws IllegalStateException { 99 | animation.setRunning(false); 100 | super.cancel(); 101 | } 102 | 103 | private int applyCurrent(int current, int frames) { 104 | if (current < frames - 1) { 105 | current++; 106 | } else { 107 | current = -1; 108 | } 109 | 110 | return current; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/page/parts/body/lines/BodyLine.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page.parts.body.lines; 2 | 3 | import com.google.gson.Gson; 4 | import com.mojang.authlib.GameProfile; 5 | import com.mojang.authlib.properties.Property; 6 | import com.mojang.util.UUIDTypeAdapter; 7 | import dev.fls.tablist.utils.PacketUtils; 8 | import dev.fls.tablist.utils.mojangapi.MinecraftProfile; 9 | import net.minecraft.server.v1_8_R3.*; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.craftbukkit.v1_8_R3.CraftServer; 12 | import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; 13 | import org.bukkit.entity.Player; 14 | 15 | import javax.net.ssl.HttpsURLConnection; 16 | import java.io.BufferedReader; 17 | import java.io.IOException; 18 | import java.io.InputStreamReader; 19 | import java.lang.reflect.Field; 20 | import java.net.URL; 21 | import java.util.List; 22 | import java.util.UUID; 23 | import java.util.stream.Collectors; 24 | import java.util.stream.Stream; 25 | 26 | public class BodyLine { 27 | 28 | private LineType lineType; 29 | private final EntityPlayer entityPlayer; 30 | private int ping; 31 | private String text, correctLengthText; 32 | private final int x,z; 33 | 34 | public BodyLine(String text, int ping, int x, int z) { 35 | this(text, ping, x, z, x + "." + getZlineCode(z), null); 36 | lineType = LineType.BLANK; 37 | } 38 | 39 | public BodyLine(String text, int ping, int x, int z, EntityPlayer entityPlayer) { 40 | this(text, ping, x, z, x + "." + getZlineCode(z), entityPlayer); 41 | lineType = LineType.PLAYER; 42 | } 43 | 44 | public BodyLine(String text, int ping, int x, int z, String name) { 45 | this(text, ping, x, z, name, null); 46 | lineType = LineType.BLANK; 47 | } 48 | 49 | public BodyLine(String text, int x, int z, String name, EntityPlayer entityPlayer) { 50 | this(text, entityPlayer.ping, x, z, name, entityPlayer); 51 | lineType = LineType.PLAYER; 52 | } 53 | 54 | public BodyLine(String text, int ping, int x, int z, String name, EntityPlayer entityPlayer) { 55 | MinecraftServer nmsServer = ((CraftServer) Bukkit.getServer()).getServer(); 56 | WorldServer world = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle(); 57 | if(entityPlayer != null) { 58 | this.entityPlayer = entityPlayer; 59 | this.ping = entityPlayer.ping; 60 | lineType = LineType.PLAYER; 61 | } else { 62 | this.entityPlayer = new EntityPlayer(nmsServer, world, new GameProfile(UUID.randomUUID(), name), new PlayerInteractManager(world)); 63 | this.entityPlayer.ping = this.ping; 64 | this.ping = ping; 65 | } 66 | this.text = text; 67 | this.correctLengthText = text; 68 | this.x = x; 69 | this.z = z; 70 | 71 | setName(); 72 | } 73 | 74 | /** 75 | * TODO find better method 76 | * @param z 77 | * @return 78 | */ 79 | private static String getZlineCode(int z) { 80 | String strz = z + ""; 81 | 82 | if (z < 10 && z > 0) { 83 | return 10 + strz; 84 | } else { 85 | switch (z) { 86 | case 10: 87 | return 110 + ""; 88 | case 11: 89 | return 111 + ""; 90 | default: 91 | break; 92 | } 93 | } 94 | 95 | return strz; 96 | } 97 | 98 | private void setName() { 99 | try { 100 | Field headerField = entityPlayer.getClass().getDeclaredField("listName"); 101 | headerField.setAccessible(true); 102 | headerField.set(entityPlayer, IChatBaseComponent.ChatSerializer.a("{\"text\": \"" + correctLengthText + " \"}")); 103 | headerField.setAccessible(false); 104 | } catch (Exception e) { 105 | e.printStackTrace(); 106 | } 107 | } 108 | 109 | public BodyLine setSkin(String value, String signature) { 110 | entityPlayer.getProfile().getProperties().removeAll("textures"); 111 | entityPlayer.getProfile().getProperties().put("textures", new Property("textures", value, signature)); 112 | return this; 113 | } 114 | 115 | public BodyLine setSkin(UUID uuid) { 116 | try { 117 | HttpsURLConnection connection = (HttpsURLConnection) new URL(String.format("https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false", UUIDTypeAdapter.fromUUID(uuid))).openConnection(); 118 | if (connection.getResponseCode() == HttpsURLConnection.HTTP_OK) { 119 | Stream reply = new BufferedReader(new InputStreamReader(connection.getInputStream())).lines(); 120 | List lines = reply.collect(Collectors.toList()); 121 | String line = ""; 122 | for(String str : lines) { 123 | line += str; 124 | } 125 | Gson gson = new Gson(); 126 | MinecraftProfile profile = gson.fromJson(line, MinecraftProfile.class); 127 | entityPlayer.getProfile().getProperties().put("textures", new Property("textures", profile.getProperties()[0].getValue(), profile.getProperties()[0].getSignature())); 128 | } else { 129 | Bukkit.getLogger().severe("BodyLine("+ x +"."+ z + ") skin could not be loaded. Reason: " + connection.getResponseCode() + ", " + connection.getResponseMessage()); 130 | } 131 | } catch (IOException e) { 132 | e.printStackTrace(); 133 | } 134 | return this; 135 | } 136 | 137 | public void setPing(int ping) { 138 | this.ping = ping; 139 | entityPlayer.ping = this.ping; 140 | } 141 | 142 | public void setText(String text) { 143 | if(text.length() > 48) text = text.substring(0,47); 144 | this.text = text; 145 | setName(); 146 | } 147 | 148 | public LineType getLineType() { 149 | return lineType; 150 | } 151 | 152 | public String getText() { 153 | return text; 154 | } 155 | 156 | public int getX() { 157 | return x; 158 | } 159 | 160 | public int getZ() { 161 | return z; 162 | } 163 | 164 | public String getCorrectLengthText() { 165 | return correctLengthText; 166 | } 167 | 168 | public void setCorrectLengthText(String txt) { 169 | this.correctLengthText = txt; 170 | setName(); 171 | } 172 | 173 | public void show(Player player) { 174 | Packet[] packets = new Packet[]{ 175 | new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entityPlayer), 176 | new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, entityPlayer), 177 | }; 178 | 179 | for(Packet packet : packets) { 180 | PacketUtils.sendPacket(player, packet); 181 | } 182 | } 183 | 184 | public void updatePing(Player player) { 185 | Packet[] packets = new Packet[]{ 186 | new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, entityPlayer), 187 | }; 188 | 189 | for(Packet packet : packets) { 190 | PacketUtils.sendPacket(player, packet); 191 | } 192 | } 193 | 194 | public void hide(Player player) { 195 | Packet[] packets = new Packet[]{ 196 | new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, entityPlayer), 197 | }; 198 | 199 | for(Packet packet : packets) { 200 | PacketUtils.sendPacket(player, packet); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/page/parts/body/Body.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.page.parts.body; 2 | 3 | import dev.fls.tablist.TabListTemplate; 4 | import dev.fls.tablist.page.PagePart; 5 | import dev.fls.tablist.page.PartType; 6 | import dev.fls.tablist.page.parts.body.lines.BodyLine; 7 | import dev.fls.tablist.skin.SkinColor; 8 | import dev.fls.tablist.utils.PacketUtils; 9 | import lombok.Getter; 10 | import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo; 11 | import org.bukkit.Bukkit; 12 | import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; 13 | import org.bukkit.entity.Player; 14 | 15 | import java.util.*; 16 | 17 | @Getter 18 | public class Body extends PagePart { 19 | 20 | public static final int MAX_LINES = 80; 21 | public static final int MAX_LINES_PER_COLUMN = 20; 22 | public static final int MAX_COLUMNS = MAX_LINES / MAX_LINES_PER_COLUMN; 23 | 24 | private final BodyLine[][] lines = new BodyLine[20][4]; 25 | private int columns; 26 | private int lineWidth = 10; 27 | private boolean removeBaseLines = true; 28 | 29 | public Body() { 30 | super(PartType.BODY); 31 | } 32 | 33 | public Body addLine(BodyLine line) { 34 | if(line.getText().length() > lineWidth && lineWidth > -1) line.setText(line.getText().substring(0, lineWidth - 1)); 35 | removeLine(line.getX(), line.getZ()); 36 | lines[line.getZ()][line.getX()] = line; 37 | return this; 38 | } 39 | 40 | public Body addLines(BodyLine... line) { 41 | for(BodyLine l : line) { 42 | addLine(l); 43 | } 44 | return this; 45 | } 46 | 47 | public Body removeLine(int x, int z) { 48 | BodyLine line = lines[z][x]; 49 | if(line != null) { 50 | // TODO faire if tablist affiché alors hide 51 | Bukkit.getOnlinePlayers().forEach(player -> line.hide(player)); 52 | addFakeLine(x, z, applyLineWidth(lineWidth)); 53 | } 54 | return this; 55 | } 56 | 57 | public Body removeLines(List index) { 58 | for(int x = 0; x < index.size(); x++) { 59 | for(int z = 0; z < index.get(x).length; z++) { 60 | Bukkit.broadcastMessage("DEBUG: removeLine " + x + " " + z); 61 | } 62 | } 63 | return this; 64 | } 65 | 66 | private Body removeBaseLines(Player player) { 67 | for(Player online : Bukkit.getOnlinePlayers()) { 68 | EntityPlayer entityPlayer = ((CraftPlayer) online).getHandle(); 69 | List skin = new ArrayList<>(entityPlayer.getProfile().getProperties().get("textures")); 70 | 71 | PacketUtils.sendPacket(player, new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) online).getHandle())); 72 | 73 | entityPlayer.getProfile().getProperties().removeAll("textures"); 74 | entityPlayer.getProfile().getProperties().put("textures", new Property("textures", skin.get(0).getValue(), skin.get(0).getSignature())); 75 | 76 | // a debug 77 | /*if(online == player) continue; 78 | PacketUtils.sendPacket(player, new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entityPlayer)); 79 | PacketUtils.sendPacket(player, new PacketPlayOutSpawnEntityLiving(entityPlayer)); 80 | PacketUtils.sendPacket(player, new PacketPlayOutNamedEntitySpawn(entityPlayer));*/ 81 | } 82 | return this; 83 | } 84 | 85 | public Body setRemoveBaseLines(boolean removeBaseLines) { 86 | this.removeBaseLines = removeBaseLines; 87 | return this; 88 | } 89 | 90 | private String applyLineWidth(int lineWidth) { 91 | String text = ""; 92 | 93 | for(int i = 1; i <= lineWidth; i++) { 94 | text += " "; 95 | } 96 | return text; 97 | } 98 | 99 | public Body setColumns(int columns) { 100 | boolean debug = false; 101 | 102 | if(columns > MAX_COLUMNS) columns = MAX_COLUMNS; 103 | this.columns = columns; 104 | 105 | String text = applyLineWidth(lineWidth); 106 | 107 | for(int x = 0; x < lines.length; x++) { 108 | for(int z = 0; z < columns; z++) { 109 | BodyLine actual = lines[x][z]; 110 | if(actual == null) { 111 | addFakeLine(z, x, debug ? x + "." + z : text); 112 | } 113 | } 114 | } 115 | return this; 116 | } 117 | 118 | private BodyLine addFakeLine(int x, int z, String txt) { 119 | boolean debug = false; 120 | String text = debug ? x + "." + z + txt : txt; 121 | 122 | SkinColor skinColor = SkinColor.DARK_GRAY; 123 | BodyLine line = new BodyLine(text, 0, x, z) 124 | .setSkin(skinColor.getTexture(), skinColor.getSignature()); 125 | 126 | lines[z][x] = line; 127 | 128 | return line; 129 | } 130 | 131 | public int getLineWidth() { 132 | return lineWidth; 133 | } 134 | 135 | public void setLineWidth(int lineWidth) { 136 | this.lineWidth = lineWidth; 137 | int maxLength = 0; 138 | for(int x = 0; x < lines.length; x++) { 139 | for(int z = 0; z < lines[x].length; z++) { 140 | BodyLine line = lines[x][z]; 141 | if(line == null) continue; 142 | 143 | int index = line.getText().length() < lineWidth ? line.getText().length() : lineWidth - 1; 144 | if (index + 1 > lineWidth) index = lineWidth; 145 | 146 | String text = line.getText().substring(0, index); 147 | if (maxLength < text.length()) maxLength = text.length(); 148 | 149 | line.setCorrectLengthText(text); 150 | } 151 | } 152 | } 153 | 154 | private int maxLines() { 155 | return columns * 20; 156 | } 157 | 158 | public void show(Player player) { 159 | if(removeBaseLines) 160 | Bukkit.getScheduler().runTaskLaterAsynchronously(TabListTemplate.getPlugin(), () -> { 161 | removeBaseLines(player); 162 | }, 2); 163 | 164 | for(int x = 0; x < lines.length; x++) { 165 | for (int z = 0; z < lines[x].length; z++) { 166 | BodyLine line = lines[x][z]; 167 | if(line == null) continue; 168 | 169 | line.show(player); 170 | } 171 | } 172 | } 173 | 174 | public void hide(Player player) { 175 | for(int x = 0; x < lines.length; x++) { 176 | for (int z = 0; z < lines[x].length; z++) { 177 | BodyLine line = lines[x][z]; 178 | if(line == null) continue; 179 | 180 | line.hide(player); 181 | } 182 | } 183 | } 184 | 185 | public List linesAsList() { 186 | List lines = new ArrayList<>(); 187 | for (BodyLine[] line : this.lines) { 188 | lines.addAll(Arrays.asList(line)); 189 | } 190 | return lines; 191 | } 192 | 193 | public void reset(Player player) { 194 | 195 | List players = new ArrayList<>(Bukkit.getOnlinePlayers()); 196 | for(Player online : players) { 197 | int z = 0; 198 | int index = players.indexOf(online); 199 | 200 | if(index >= 20) { 201 | if(index < 40) z = 1; 202 | if(index < 60) z = 2; 203 | } 204 | 205 | int x = index - MAX_LINES_PER_COLUMN * z; 206 | 207 | BodyLine line = new BodyLine(online.getPlayerListName(), x, z, online.getName(), ((CraftPlayer) player).getHandle()) 208 | .setSkin(online.getUniqueId()); 209 | line.show(player); 210 | } 211 | hide(player); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/dev/fls/tablist/skin/SkinColor.java: -------------------------------------------------------------------------------- 1 | package dev.fls.tablist.skin; 2 | 3 | /** 4 | * Provides the Texture & Signature for the Heads to be displayed in the TabList Body. 5 | */ 6 | public enum SkinColor { 7 | 8 | AQUA( 9 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODAyOTM2MywKICAicHJvZmlsZUlkIiA6ICIxYWZhZjc2NWI1ZGY0NjA3YmY3ZjY1ZGYzYWIwODhhOCIsCiAgInByb2ZpbGVOYW1lIiA6ICJMb3lfQmxvb2RBbmdlbCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jOTk5NTQ4NzJkOTM3ZTQ0NzQ2NWQ3NzI0NjFjNDkxZDE1YmE2M2U0M2ZiOTFlMGNmZDkyZTJjYzFkZTRjYjU4IgogICAgfQogIH0KfQ==", 10 | "rjW7ryxH4hy4L7hwUlDNhhfQQcAbC2EONvB2e/foVIUvI0nnR+GymetSy0gDvQO8Hor57zontt/fHn7W+i314Kf4b/uqK+nWKUVubxDpwGrZby5fqg41wpRj3ipiRze3cKGIM2TN6J/wZMi3UJxzQuJzzW24jwzwcak29RsCqKyCzGVvBgp8AvQxJdmTRbFW9RBFOYB71H3VPdvbn/ASL//BEJsew3XVNWwmizDDKwuqPvFIteSioNPUC3hjXBXXUPQ/sxVjvei014jyY9ZelMrTYsnD6ocutHfdztG+Jm5q8HFfYr5WxZA8093iKJ06tb2/1izy4aXk80gsrUnG6G6aojH4o3LRS7Cr8MKjCOA5EIutlJpOXMyl+Hn0e1Advaj/cplic6OKGXGwrNzeos8JUH4TEw3034I/Dlwry249t2uKlKOfzXErOpCKbpAeW7hVNDjxqV7i6tDf/lnyMMgOqKVd9c+BxN94rB+qFiAhuj6qL5mmgcYHuL3OHQ4JIYa72IdHiTnBTFQ7ODwUfWQSLii92I5Mazn8uiauvcnvB2RSltjyHomVydpkX9RNAsGg1gYkDfvFPlPNPJZEXT7sYRyUJmn98XddtaUX12PxSh04A84J0dLoNUuIIS3Vi98jxC2MUgVkYu38gSOqFV+PJo2L6+ZolIxe58u50Oc=" 11 | ), 12 | BLACK( 13 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODA3MjkwNSwKICAicHJvZmlsZUlkIiA6ICI4MWU4ZWU1MTE1ZmE0NzU5OTU1YTAzMThkNTNhNjViYyIsCiAgInByb2ZpbGVOYW1lIiA6ICJDb25zaWRlclRoYXQiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTA1ZTY2MDJjZTg1ZDU4Zjc3OWY5MmU3YTFlNjJmMGJlZjY5OGI2OTk4ZDEyNjUwMzVhZjdlY2JmZTJlZDE1MiIKICAgIH0KICB9Cn0=", 14 | "Snd3AC87Q57jlpmZO6yMOAq+vRdW8YR4LDuNGBBGFxVQ9XOzAWMoxCALcE8GeF+/zb5SUrcEKDIrPELcpvRqDmC5YyG4+QCSMiFZMObf2SSVHNqjtiQp3pbwjqUagEjIHPJyvgO/eQ/tDpccRMev/jx+pW7f7G+Hw94T2r6eQASQ4CAwtk/unWBCmbgujP6398aAFrPcSGtoYsm1R1giY43jcGn8Vhs6DMVvoI6kC8D/tkLTGon/d1T/iGoWe6rLayXGdfZPyw6HUUFIIU83vUdnSaZWR/H5OTZ8EeblF33UuwUVG8kq0dgx5BV5IZhXMuBTwnH2zFXI0P1mF4SqOthLgfHwGZ8wpVdMce74ft0wgbalfN5qTuVMzC5nvBYbNRNS9Xq3N/nq28V/BqtBaaJsb6aqJW8wtNGAe4nL4WShkRsnhm3uEzvIteWTWbxjDHHgDzmpHPZ6lUTkoqYOaYdQycHTGqJwYSbgmGgHdGcGqifiJdDBz3cCID7mNMBWTIfkxh1K3//huLJAUB4rXh7wPJQclaDlKYMO/Qegq3ci/EYeLMBbj7J1OvufUG5/OtpdyhowMUZqqz1xkEZ/+WlWfS+TVvnHrGSANSl/gobUuYPRGeF1JUDURjxSoDYQXDxOe2k7P9j27Z3NNgyVhOKzRT6FWz4pzkvQT8U/Er0=" 15 | ), 16 | BLUE( 17 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODEyNjc2MSwKICAicHJvZmlsZUlkIiA6ICIxMWY3ZTIxMDMyOGE0MjRmYTU0NmFlZDk1M2FkNGU3ZCIsCiAgInByb2ZpbGVOYW1lIiA6ICJHb2RkZXNzd29sZjEiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjllYTAxNGM4ZGZhODdjZmM0YzNkNDViNmEwNDg3ODNlMWRjMjBlMGNmMDFhMGEzZWYyYTYyYzhmOGI0NiIKICAgIH0KICB9Cn0=", 18 | "algyza0T0nJYbcg302PQyK4cl3H7XEeG2xh+2uXK9mn/HYzb7vYGGKIZy1sZm7HempV4qfOVuUdCCMSeLjoTwGjnw9mnaSwsY/ytTBI3fPCVLDdZaxfBU7qUGLRIc+FT2MEUEmKQF/cr/ku/OqBmrWZPdMf8Z3EmUS1qW4jSc/pHTw/+82K59RgTO2QocVmBCzcF98t/g61Lf0avkh1xsdbVCSX7O4BWMasofTd8PNRiS7XElFWXWcUIw5DbMVwjPRB+kXk0nFt/l3IEjrnOGA+C/mUMmi2oNKucoSNLiq93HyXycXc94J13zYO4jM3Qc/YkRAz2SmN5jcD+wlTYnuaMojfBVRvmuT+KlDUY5EUwAHJkT3BLpk57I6qf1eeowzoRxjJAR1nZOZCn9NJjp06GV6xoNlAIuQNSmevbM2VbKoMoWYVgVTkqmORPwaj0CEjOMZrV39hCJKjFCHxr+PttOArjRcGW5sb0cLhrw8MSafGryWnX/njftNRvbr/eXwzH2JLqLFIkMAq0ixwhf3X8WTxam32lsQvh1BrWK13Xq5lcEMU2JtMggtOmwwJqm3EsWMLehPzQknt5zgyhbDVA/CKvfuarWmGBxgcmmlsHGzrgmBVUkW1Mdz/kT+C+DcSG+I7PZgwiklDsU/uRuWqCJl3kvBOI4umuiBSwplI=" 19 | ), 20 | DARK_AQUA( 21 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODE4Nzc3OCwKICAicHJvZmlsZUlkIiA6ICIzYTNmNzhkZmExZjQ0OTllYjE5NjlmYzlkOTEwZGYwYyIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb19jcmVyYXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2FlYThmYzhlMmY3NWFjNmU3NTU3YTA4NGI2MmM0MzgzOTI2MjA0YmIwZTNiZWRmNmNlZTE2N2IyNWU4NzFiMCIKICAgIH0KICB9Cn0=", 22 | "utHgW2dYl7gGHHqRkut8kZ1fVE7YrmDe5wSMaMpfN6JUPZJBPkb3HboLxU0ag1LvWZQpaYmXnrKM5fMihATlc48BjeHfzrYb1/zIQPF/MGazLqcRZ02Fu5BNZfPbf8ltlJInamhIrfhpXIoPef//gv6zP4PEZkMGgXg+ZZD7cVVi/4ug21m4xBQcix8tCYipNS/voQ7HzEJ9P7nBl9/HXLQRTlEM8AP0nuivX2BjqTTb35E0+0IyY/ZIWhQTwIEWXjxLfhlNQGkQk7AHryaUdExMN4ProOuIaU0O+BsbchI4aPWWgdzq7T0urjmN1AGFPjHqGca4a/pIIYjwtKqV287Jnxglkp2kXjgH6kT25F45xplM8F5HmvJVcWVJmfGhIzMEyxkaYYALwIrzh2j18ejy0iLFZ57rcNq88g32M1ll5yoULtTKbGkafR70Am0E9yABzu91KFXmDgkDQlwMFwpcdTO9BxiNvzhKeArAAHL0PgZwnf2DiysaZpwmLiwq6sKgaA2HpqsaxwwDK8qBCseTKnvOk5D8IXTE0YDWScUtcJQpVpRYlNPqqCubvdRlw2N3/t2ltqGdI6euSz2TrGZT8HrJ7r7sS32lY5hHqV8Ua07TLE9bQZYy/CqtpomQ3MkgFgZx++0PxhRMoiuL/FNSWl1uWpEADuFE9W4yef8=" 23 | ), 24 | DARK_BLUE( 25 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODIyNTM2NSwKICAicHJvZmlsZUlkIiA6ICJkODAwZDI4MDlmNTE0ZjkxODk4YTU4MWYzODE0Yzc5OSIsCiAgInByb2ZpbGVOYW1lIiA6ICJ0aGVCTFJ4eCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS84MDExM2U4NjQ1MjVhYWQ0MjU0ZmJlM2VjNDQ5MzIwODRkOGUwZWRlYWVlMGE5ZWQzZDRhNmI1NzQ2ZTBiM2NkIgogICAgfQogIH0KfQ==", 26 | "lEZgrkxkSZE/s4zt0Qf2wFdKLvvHG2JxU4WBbsRRgdcK7EQFi0trhNoDK5khMqnQwwCssndL74vFk9oVcgoiXk/FtCBaawPeqZicdxkqqSh8me+BTmjK4l86akPAKVLXIfWjq8qLwEapAkEP+Zc6qMw+lYJwhy2gzt9cxUK+ALHarMKJv9zo4xU0WbZsLGE5PF8LDWTdIBErEnVl9CStZ6xBRg7+OLIofVka0nd0JF8cYLyHtbXYmj+MeySbCNTgqab2TKSRbPW+Xwbbc2JXOJqwXESFEsArlS+lCTydSwZK26QRfzXp/OWDddLculyutC0IO+yeGDZQ//XKDuvXM3O5OsSVYUCcett2AOYYUV+519UM5sARhfJL/F4J1t2W8TSvIIf9cb+usi/HjaSDfFCh1OPz0KOdggZX3Jdht1ssANCDPem6ZmLKN8tg4zq6+kGgEaiCoBOY86kai+zxOKoj2jxKeufw7qmE51J8MNz8yLfZWMdz9LfkKCIHxSpR+UZVCfIFevCu3Y/oCsKhyvpKYNNAvtBKDJVYcqSuQoZ9gx0PTFmvtZ806CEUauQHHI/W+ugZavz/8TjweF4i+2lob75rQyzt09cE9qKbFplStgVMxGJF7w0PEXIs4/bGmS3L8GkaaS/H6D5WQ1AYlGR8JRGguplgTS43uGqLQc0=" 27 | ), 28 | DARK_GRAY( 29 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwNzc4OTIyOCwKICAicHJvZmlsZUlkIiA6ICJmMjU5MTFiOTZkZDU0MjJhYTcwNzNiOTBmOGI4MTUyMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJmYXJsb3VjaDEwMCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMTkyNTE3YjZkNzJmODMxMTU0NzE0MzdhYTlhZTgyOThjZmQ0NjQ3NzE0ZDg0MzNkMWFmYWI1OTA3MmIyODRlIgogICAgfQogIH0KfQ==", 30 | "AOpMmlO/TEBswd09MW3RJzjH3W2UlpOyQkW7VPWyLs1wswfIOHBda65iL4gHmpBPmRhnK3HeZAVCHnSxSCL53kC1b5DZS/RTnZE4jyY7G6TPLI8frNfAtKB7Zr+I22QsrvAUEhyRvgPFNdta5sdrq9DxBPhMgApuAxlwPlXoN9mEkiChFVW/EM/WqqYJu97KR9bpJjbWy78M1moVf+X+QZtB31KBbz70CrCXInjbBP0buLLtgLzQW4+wDw0UfnEK4bHbLE6U2lnSPvPeACWQjNeGoabp4GS8xCp4CH9f9p2jljrz9zpX4eOEXLvnGFix7sX1flEKT5ad0Y8zioc8aJRn5VBXKzcsWU/nDBo6rPjHkic5OtTOH/YjK7siYZa2aYmgmjob5yl9HV+lOCXyFwHBEXhILpw8e7tcyrnjhQ3tKPpbgLNJaIu43Dv5FntNxc9nDnC8EAy8/WMQ5RQ1kjUF9EmccIaewNw/kzIxKar++I5JRrCDwaTUK9/n0BN3vx6dDiC6y1hJrOjiSGz+HG4bqySDm1KZO/3GjoXmhE5KhFQVy5Rbrh5eoGqsBCR4dvrwYwbPWcbzVjoeVq8qEzHn+qkY+DS3X/b1rVpC9DyVyev0Bejg7seBDC5Vqlyg1tPJmE902Sv+s6pjm3IYfmj4YojtVOOm8p+kX8oYmBU=" 31 | ), 32 | DARK_GREEN( 33 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODI4NjM5MiwKICAicHJvZmlsZUlkIiA6ICJkMGI4MjE1OThmMTE0NzI1ODBmNmNiZTliOGUxYmU3MCIsCiAgInByb2ZpbGVOYW1lIiA6ICJqYmFydHl5IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RhMjRlM2Y3ZDEwNmM2Mjk0MzY4OGUyNTMyMmIzNDZiYWU3ZjVhMmNhYzY4MjM3MmRiYzdiZTU5NTk3NjBkZDAiCiAgICB9CiAgfQp9", 34 | "XE/boxQokPioG9He/+513JSoWT8hTzaAnynF/qiRK0+zLAI+FhtcGEep0jZ74FFpKRTkyiI2GVfLXcZLpUO0gZetRYI3py0VFs9F6mWWEUWcafZh+XOmHOwGcEBpzZc24ETWz2S/mdOGbUsx3TBubeJ6VZwqL9VAIoWPxd+d66lzxA5yth/uPNsomVb8UsKzs6hjL1Gq1p/CyUj6xMpQTVzWS2/JI+v4yCTFTcm1A4PNgA/l4yw51nOC9betlR9lugbTYgoNNVwwM/4WRVgsghJRZBs7LkSFpQaoxSXBwRDK5UolM9AXyaatKqKBX5V7TVPxpaNUrsyDPWvR7Obqu4z1Ujaq2BqUdr8+pMuKmGH/Dr/5jDus/L9kL+PCXKeuFuCuVFD8fDWXq/0n3NNfZjhdttuCrVwn4aChNFVNl6A+sub+o/M259o8Y63tt23HszxqwpLbUBg+w0oyib1EvAlFFu4KlsTVocpQ8MFFCY77lXidSm92nARw9k4Kx3Hr+memiQ64qKctD7qHVegh+CmcZGp4bIlEVPU5p4TWLgTPzNB4Nhe3EwWsTIpBYCIPGYzhrAo+zKbeYqIYAeExP0YshT2lOn2Od8xeWNb1/VirdAAAV8+kOwzNL79Ukh8kZqqJ92XSp0LT+tmSwQyslKl88piZe08Rgh8qNQyZlDQ=" 35 | ), 36 | DARK_PURPLE( 37 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODMyNTIxNCwKICAicHJvZmlsZUlkIiA6ICJkMWY2OTc0YzE2ZmI0ZjdhYjI1NjU4NzExNjM3M2U2NSIsCiAgInByb2ZpbGVOYW1lIiA6ICJGaW9saWVzdGEiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWM0ZWZiZGMwMmVlNGQ0NTIxNDgxNmFiYjRmODZkYjk2MjY4ZTgyZjhiZTAzNjNmMjRmMWQyYzU5OTI4M2EwMCIKICAgIH0KICB9Cn0=", 38 | "LGh1b2HjHP/zFFkmlypeoG44A/B52nC0Tz3yMk/FQB0raZtjgXLiliPpGoEJrG82WlxyPoHlczZKJI1eYlzLrP9scWfeF6pQ4uwpZ36mS+iW0foBm0V9YW/I+h7VmGA+aIHmx4avSECD+wrgxohEOrpwEm2CtVmVtPR/yfSJAb9W7E5B2Sf0fO3pZFMFjjtbe18wbe783/jxLqjxLWKs3ToYBPrjiKcv8J67PZ1i9olugdH1nIWi5/gYQpMgezpvxDNHVhMSnPHDRDPDIE2aAkDE+s7wNPqP4GZaOmd47Y14TXSZSSnuWNPSK826gi3jd6tMd42R9nxhnQ1hF9rVZYIvw4+OXhdt7OwU9IeivKldl4YWZjm452sPIkziqJGe5CaZXyaTQ0HuCZFJ2e9Fn4RdiRzsdqIOwx+fIQWNoSa9UKR8oP0Eru5oHAguCjfkwjKmBRUkgRHI2xu++B/gzlc5EDtaViXcd+i/6U2F6sfGYxdgSrkHlOvpER2o2jXEX0ar6c2wMOPzSy3JEU/McwEmQXKKz5Y4GkMn3SCoj9n+ObGnBtD48fGPCRXC9IJuupOJnK9pi7iGhbRJ3vC+C2lIa5HhciPfd8oe+6weg/uDdY8houo3gF7T24ZGN9zzj/tWYRW6PHuP/IIFGDtXC2+5c18Ek3Dq4fCBV6ARy88=" 39 | ), 40 | DARK_RED( 41 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODM2NDc2OSwKICAicHJvZmlsZUlkIiA6ICJlZWJiNjkwNTk4ZDU0ZTYzOWVjMmQ3MmEzMzlhZGU4ZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJpQU1fTU9ZIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2E4MDY4N2E3NmVhNThkMzIwY2M2N2YyODdkOGNkODZhYWJjYzcwZTQwNDVhMjM0MTY3M2JlY2UyMjYwMmYwOWMiCiAgICB9CiAgfQp9", 42 | "qkyWUTGZ+aQR0gKVy0jC+sVjYZW5NyAIWci25LzAhUlGox00ZW8kZLoYV3GsOckf0NTseXeYF7M5TS8GPeyAFxxWPnp2wUGKYqAIYIgY6KgQ8fulrxqkBtLxolh1uTXDRXoyViCaUOqNMbrqdEfuNPkKh6C0PFheEy6jdC16t2QcLDlvhr3rssrUdval21DjEWsNLVG0YspyGHIN5xRusu0tZxNF1iqVAseswmPDeaVNmqEg5ApLxtoDlLuqYtXUAkP6CuXg9oAaJbYvSXrjmaU81tZu9OfYE2rQ5QdKYPp5/2ZgNbsrjsWYNGwVnYDZi1IzZ1Tf7KigHc7yxqKpRrzWLxaks/9Nxu26KzK9Fw/W+aWyL5s20fPzpqh3CY9sbRuE8iqCUO8YpD0s5J2kZBBrzt1G88TRI22PXkNYNa7zQvVnEdkKbjx3hEgRt4TB5OiZXKLeOLYUgpyPX3ZwBe2jUT7JSubHfafKySWQ3QLnkTfYfks1E2bvEXUZghUM1eY9hLsLPCZoyiCeuHnt5TUxc6Yuc+bQeHE2qCSfJx13dlGVpNXvsM9mhunSw4DPZjVWNmyQgzdIp4CFfBEKSQeSvHIQUtJq5FuVKm6plsfhcYtwrsaQ/9xEHhI/MsbnJfOVTTVB+O2M6vDULprGA5jFwVxXjWdX7q1n1mv5Azw=" 43 | ), 44 | GOLD( 45 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODQwOTgyNSwKICAicHJvZmlsZUlkIiA6ICJhNzdkNmQ2YmFjOWE0NzY3YTFhNzU1NjYxOTllYmY5MiIsCiAgInByb2ZpbGVOYW1lIiA6ICIwOEJFRDUiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzI1NmFiMmJjMDU3ZDM5NmIxZWVhODQ5ZThiZGFhZWNlYzU4YWJkOTQwZWE5NzgyMWMwZjk0MTQ5MmUzYzAyOCIKICAgIH0KICB9Cn0=", 46 | "JBDE8H3V6U9TMP2qxUpf06xzyVrayqE6hDp/x5AhZ8D9kiYlSPaWvkyhv63qzLrcF8NHhPp2NamwcPfkmZ40TZrEELjcMW7HxctxZtemPZ0OpVBLleW7qSy0Ta7aJ/H0BHiJ8fTWg6OuwMEwCRzC1qvr44dyCn1vZkK3Jy9+mlORga+IDcby44n36fpt0zc8w3yh2L3wlZjZg3ON5ByD+y2JmlgUdY8VoSzNygIycT1CrtHO4oBWEirquRCy7m7sAqlPIlOsfGNWVvBMcv6M3ZyeLymKoMdpFzYPW+n9lakQ64OSbIhfuXHXXFGBdQZNoW9LleOG8mP0Dqri7G+SNlQXyTqioGKA7vMH1/e/ZEuZtAHgdncu6oU+4+eVBb0aq3P7cMs8kirg5puLWG22Jq/f4CNZWCHRlkhbjMWKgA4aYmTIwKE2arYQ7hWoqkC6+a556kqNQC23zXFR8fnRL0qSQHqx0EKsKK17ITYXOUtfrJZ3BTqlx4+7zV454DltPkBPyrHwhlFFcFlTJAMrepXY85bTHanNnCJOMuLDKMzEE0cRvqtnNe2kwFVPwSLMQNFcCroMaOgw3XDlBeLpGVWKkhZDA8KlAjEZxet7amhHKXOOLNHlhuo7EwIRrxqxEZDXeuiGr5oWUbC7fm4E6HSt/I+tW52Lq9g8Rt4Fvy0=" 47 | ), 48 | GRAY( 49 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODQ1NjcwNywKICAicHJvZmlsZUlkIiA6ICJmZDYwZjM2ZjU4NjE0ZjEyYjNjZDQ3YzJkODU1Mjk5YSIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWFkIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzU5ZGQ5OGNmNWRjYzQ0YjBmZWFmZTlhMTZmMWY1YTBlNzQ5MjU2NzFiOTRmMTRiYzFlNThiZTllMjI3Nzg1ZjAiCiAgICB9CiAgfQp9", 50 | "Y+LeppS1hEmuBiHKOITcy+HclZgBuWG4UIYhWm86UF3ts7cjOYu7EVHWEOXwWS49z/BZiRpS4y+WO/uk0bl9EksLk82m+fYve3Wfdh4Oii09RTvEhorNTNzxdApwlEaTmmFKZ4f7jOzdzbXe4yAb3OGMC/d4t0WDZ+ZMC84PzC0iYRgHUVMqis0N3RgoseNKe7/1lE5trrVmvoa/T8AsbHsOedPElhaeNWFCXYHTtsGtl611xTFftP+A4XLFGN/5oLHG3zahaqGNdscqtOy/WQWkN18HJe+v1bhzBj5XtmHRuTOJ5Jg2m3pGozplP7O8C0CwfP37hv6ih7wxlDjKGe57FW6jdzfBI7JdeqiHZGb2hC8F7S0l7cz/+7phkaiPgeEewyZBa+3K5tA89Ohd2j/TsRwceJJplA4+vyj2oUDXsHhq+5Xm//ys5eY4vzGTvwFOwAlF4dfvE8ku0XW5cWTLp2b52+z4wUOx8lIiBvfrBxsSdb2/5R7QbP2LdRsQhxGDBifEp4vDQKiGNbwDYn9i6/ERhvtehQlXsLXUGeqwIxuokN+VLlLrDMcGvTmE2oI7778uxV3l/3TEIG7iC0VMXts6Fs3O7cAXX5Z1Ph5I1CHM1O0+zKuYsSxP4sc7mzs/Pp+O5Fw5GUNMMuwv9asN1t6RwBGsBjRGmEUXVbY=" 51 | ), 52 | GREEN( 53 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODUwMTcyOCwKICAicHJvZmlsZUlkIiA6ICI4YjgyM2E1YmU0Njk0YjhiOTE0NmE5MWRhMjk4ZTViNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJTZXBoaXRpcyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MjFlOGZkYTgxNmYzZDQ4ZmE3NWQ4ZmRiNDdjMjAwNDNlNzdiM2RkNjI4NzkyMzJiOTAyN2IyZDVmNDQ4MmExIgogICAgfQogIH0KfQ==", 54 | "S2Mf0hiAVBqHUpl9DUIwxSxmghfI7o+EL/wZ4lEmiT1R1hzV8M8vZZMojO5FhBvzQcsFcTqb8421BJcMQYNJUVQDAmoIPHq654d67OP5GY5Iz1JxpsCpEkwzn++KWZeEmYl4II2z5U114VHKDt2pkRIhxPJmWmS3+VvSFMmPRYBO2X107jB3hrhKhRRRcLURvh9ry0b47SDXfzHF+dKP6BlZBtuIgaQ/IQNmqPH40+riR4ng3RL/filNgX/M7VWjHwaLoyiv7k2R7vW1jrSb2g57CVZEH1vQZ5+86MRHyQ+061yTZKXlt1MWDxQDnvfmmcliQ+1pKJ35VaaaxxzdWnlbbx3hh2dMZjGKuYtXpnPwDUJ/OV7iBGGd2SdjyVdsfKkP2ggdemkap7eZpcOVxek0a41/S5R9E5p0nIG+zdkRZ7W65fgH+H0oyZa62lnsSCAzSrI/cnz4BQthgg5dfUBS7R2m45ISty3vDVSYTAD0VAtjVqiNJ0pmU5qO4Jo0RFbj5I1HDiODqgDfbcKNCUgaJEedlbpcCOlPtUM7xVpRm1fVMfufyMJtZ3ZCdDB4/RNCRKqsCAbRrd7g54XI5eNA92t5mfUBi41pwM9+W3MBZQlLbs9+5rrTUwlO1mYcxQlzznhwC63Jw8HN9Usm3oqi1xNPOsdLWQqtB/oo8SM=" 55 | ), 56 | LIGHT_PURPLE( 57 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODU0MjA4OSwKICAicHJvZmlsZUlkIiA6ICJkMWY2OTc0YzE2ZmI0ZjdhYjI1NjU4NzExNjM3M2U2NSIsCiAgInByb2ZpbGVOYW1lIiA6ICJGaW9saWVzdGEiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjczYjZmOWFkMGU2NTEwYjU2M2ZkYTA4NDI1ZjNlYzVkMTZhMzgwNzI3ZjE2MWIyYjg3M2ZhZmNmZjQ5MTZjNSIKICAgIH0KICB9Cn0=", 58 | "LflnHGT1KAcJAJ0LviXoJ4LsOymUhIkJmap3djfmYMB5+U8Skodc9dkmnMIaU7K3jAKrM87S2YJn22ZnDYPGXRbkgbJMrh6/hBeaEoogT/ZeNZcmo2iFLZkQeRdQqnd1b62lO0XKKdPqe5JTosNvX6+xJxkFl81/+cZKTxmyAUSycESYAJH4Vv/kY1dygH92O4vCf+FPjOOHX2Xy6zzaHOiJxqmGA0QcGWpNdrhoxORu5H4/FKdrjlqw72TinMC8KP6OBfLez1XzggaWVAHKAR/ry4dZ+YgzIJMOrt6xAgqJpCXt2STeLyqEMSLjSBP5J2F7DPImWRgmEuigYeXHWTrouVCTHBeeFfPRQ7WBq0gp0tEhZA4JgepMvg+sFthhPLEFuKifsTkeN/Q5/d+UxqVchQl/yqhY8Ug7aaUaMorqdPCp9Em3+fM6lASeUPOzlqt4LblMmZVY1P797XfUk+GkmNiUKUMo4lJAcahUdl3ja0msYNoW1l5id+yK22UWlA9riS63SSHch56+8N8uNCniaaq+HlMeIho3SBeHQGpb/nlycbA5+440GQEOsbqVwIY73eeA9AGf2Ose8BdPGgI1H6jECMB5l18lJbslUygUYY76sRs84Tbbh5/cpfcelS8qdkTx58P86Fi5Wl5Mxv9zWAzTueZNCyHMuivsCs8=" 59 | ), 60 | RED( 61 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODU4MDc0NCwKICAicHJvZmlsZUlkIiA6ICI0ZGU4MjQxNjJkZTU0MzU5YWFlMDBmMzQ1ZmMyZTY0MSIsCiAgInByb2ZpbGVOYW1lIiA6ICJOYXRoYW5fS2luZyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hNjcwYmI3NGM5NzkwZThiOWVkNGJiMDJmMzBhZWRhODNjOWEyM2U2OGFmZmY2M2JhYmU4YThiMTM5OGMyZmRjIgogICAgfQogIH0KfQ==", 62 | "CviCV2EKBkM0K0jpr90rCX6l9ij6rJopRsiY8nTiPeDWgKcQzONVfiGQ56HBk3W5EaTqGe2WJuqF9AaaDiuYryhzam+p8/cUElxMlXcvkKF6BTNkWu4VqnfphUIAuUI7rMx9Ow1NEoObK/uWiC6kkxOrDF83ErxKsgCCaxqEy845Ii0OGcIWpwOqPRQD8Nu5XHdKlgI34aXApZVnSwfyrmrO/fmgD45/SW5khuVMPTWfReurhoVgJ345FNDlda3hFP5MI3wBxnlomID2YYjmVSfyXZM1/wYtIUrY0Sbt0948jlxZe0SL8uwek/fZDSoNPMm7r/qRClz/x/5EUuaak/tNpnMdzFWLNdNzyUwcAwzGnT8BuCaYKRtnV5tQH+XTehmdt8Fx0zsO3vRgna06JtLXwXro9Wz7CuQrgvIqZePjD95+stJPbW1eC/4SVOiNq3o+HIICSxM0cM/M97AAwU+8E/nriSNXEuqV+vmrQz+aKkiEOdiPed1Caxcghk8ZGsdmu0ep3zttoSDSfadIE6zw+4C0yVJ3qPtX1xpQW0lAKSFWqQ6Nht6mEIr1QpY7pys1y+dig7G2hTUeabjbZq/E/1xcLTgNhizomtHozlm14olTVWeqmgnyh0doLaNR0r2++pUICGvBGqODQa6dTcrYy5xozzs97EYGMQTe6QA=" 63 | ), 64 | WHITE( 65 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODYyMTcwMCwKICAicHJvZmlsZUlkIiA6ICI3NTE0NDQ4MTkxZTY0NTQ2OGM5NzM5YTZlMzk1N2JlYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJUaGFua3NNb2phbmciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTZiZjE5NDNhMDVlZTY5M2FmYWIzNGJjYzg4NWNjN2VhM2RkODdiNTU3NWU2YTM1MTkxNGRjYmIwMTEwNzJlYiIKICAgIH0KICB9Cn0=", 66 | "ja86WuvK0vrQyQfWunZU4LWZEsGp7uUZhusIkceT9dsan68+J31VVUc5WWUgqGBjR2V2yOR7hl8lKjX5HgtLOjaJsYZx+TU6yyWa1HZ893Ag3gBekJOGd4B3h+4E2H/xTXZo3zkEDZHSTiCCWRdAZq4/t1zrZXrHhqftQHzOx32uWBmzMIm+WqQfU6sv0MUGDyUOYvKNndHeJLOcHk4ZoU0DL7nOlTJqSbwHnsz7MZ/nqXpquc1/AbceQQw2ooV5zYFYymOzuayei4+5SfIKFLlYb7ZUFS3I45+73KqQShaT3Hh3py+AThNTsHZL/LsFIr6/MGV0xpj0jQuEzwdjtPG3wbjGroe/xYT/L16i/pC/6B51JaDh82+laRHb4f/Ts3m8EPAQFmJUxipOosIVCYmgHfv7zlBIAviLNEW+p3WqdAaetEotNo5XvdePqZbB9z++ZOKStL92GAK3rNmzvRFC2j4Hdyt6Kmaod69yXK/eTsL8wr3DmNnVV2kIyOiPV/IziUbrV9enoYJvty+LprbOPzcbpt0tNKuYrF2OOY2EjRViJo+oO4QlvIqHgmK+MWBnRXuNkJSMuFo1qWIUM/qwFp0bX6edJXu4zhZeRDQJRB7WUqroDobQYjhtydQMdlei1uuyfFybFgWI6rjZsdlWbSJNE9yffYvElP1V4ak=" 67 | ), 68 | YELLOW( 69 | "ewogICJ0aW1lc3RhbXAiIDogMTYzMzcwODY1Nzc3MSwKICAicHJvZmlsZUlkIiA6ICI0NWY3YTJlNjE3ODE0YjJjODAwODM5MmRmN2IzNWY0ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJfSnVzdERvSXQiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjcyZjMzODEyZGEwZTlhMDg5ZjRjYzI1ZGU1NTM5MWIzZjk3NTFhOWRlNTE3NTQxYzkzOWZmNjk2ZDBmMDc4NCIKICAgIH0KICB9Cn0=", 70 | "K4TNsYfYBawAh0M3OydjtqA405e3g+KFt8D+imqw2VMGtd99ShbdO57TmQMvgUxQFwlcPFBIqlQ2uGU12nstIS3R9cCXOhdEnY3d/7VUIxb2pKPIZhUB0PA0gNL49DaG+IR+/28roOV76Nai2fjvE5STWWhXvmlKWmSExu2eQu5alWGR8aSJTYLzlIVr26JazIWYBNOW8DGPMvqy6/dDKR/ASXuDs67mmb3aNyqp1TdX/I/NsabstD+7+GTT40vgADyFA8G0Oj6nXX9xoPYDH3dFbJR7xJRFv6293HoOO9x1uJIaM8VfLZR3SpTg1mww9CfwWMRqLM/oUGwVV7tbab3O5baYfPUSmwDva/vGBISruWVzVO+8TQNZBSs7ikIZO+MLzVO6OWHCAfRGRZgASmXzgFP3D7QPvxKh5uOWND9A9LfSa25y/tBWgRHLy/Pv3JDdkJVomYg5Pz35OjmDwS479PW8FqjSebG5SjefcjnL7MlKfBTyUtn/pz7u3mTTaT97NkzvoF3blxfnugpOUY7qc+mwH0Cf0nld769aOcWgz2WvyueeU6m3EKHpY/2LUhECwe2EayJ1eFtxUDIzJfBLx2nk8TrvXLv0XSsZabms94tWYazeIaZkgCV4G/MBmgJBYtdjMTzU7yXu0C7MbfDkJE/aT6EpdjdyLCddDmk=" 71 | ); 72 | 73 | private final String texture, signature; 74 | 75 | SkinColor(String texture, String signature) { 76 | this.texture = texture; 77 | this.signature = signature; 78 | } 79 | 80 | public String getTexture() { 81 | return texture; 82 | } 83 | 84 | public String getSignature() { 85 | return signature; 86 | } 87 | } 88 | --------------------------------------------------------------------------------