From 293bb4f9cd34a46c3b33997491b4b7927d0d9e70 Mon Sep 17 00:00:00 2001 From: syeyoung Date: Sat, 8 May 2021 20:24:13 +0900 Subject: cosmetics. --- .../commands/CommandDungeonsGuide.java | 5 ++ .../dungeonsguide/cosmetics/CosmeticsManager.java | 74 ++++++++++++++++------ .../cosmetics/CustomNetworkPlayerInfo.java | 65 +++++++++++++++++++ .../cosmetics/CustomPacketPlayerListItem.java | 39 ++++++++++++ .../eventlistener/PacketListener.java | 7 ++ .../events/PlayerListItemPacketEvent.java | 30 +++++++++ .../impl/cosmetics/FeatureNicknameColor.java | 2 +- .../impl/cosmetics/FeatureNicknamePrefix.java | 2 +- .../features/impl/cosmetics/PrefixSelectorGUI.java | 2 +- .../playerpreview/FeatureViewPlayerOnJoin.java | 21 +++++- 10 files changed, 225 insertions(+), 22 deletions(-) create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomNetworkPlayerInfo.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomPacketPlayerListItem.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/events/PlayerListItemPacketEvent.java (limited to 'src/main/java/kr') diff --git a/src/main/java/kr/syeyoung/dungeonsguide/commands/CommandDungeonsGuide.java b/src/main/java/kr/syeyoung/dungeonsguide/commands/CommandDungeonsGuide.java index fdb4da18..f1455ba6 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/commands/CommandDungeonsGuide.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/commands/CommandDungeonsGuide.java @@ -39,6 +39,7 @@ import kr.syeyoung.dungeonsguide.utils.AhUtils; import kr.syeyoung.dungeonsguide.utils.MapUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.command.CommandBase; import net.minecraft.command.ICommandSender; import net.minecraft.util.ChatComponentText; @@ -367,6 +368,10 @@ public class CommandDungeonsGuide extends CommandBase { } }).build()); + } else if (args[0].equals("echo")) { + for (NetworkPlayerInfo networkPlayerInfo : Minecraft.getMinecraft().getNetHandler().getPlayerInfoMap()) { + System.out.println(networkPlayerInfo); + } } else { sender.addChatMessage(new ChatComponentText("§eDungeons Guide §7:: §e/dg §7-§fOpens configuration gui")); sender.addChatMessage(new ChatComponentText("§eDungeons Guide §7:: §e/dg gui §7-§fOpens configuration gui")); diff --git a/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CosmeticsManager.java b/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CosmeticsManager.java index d7c8114f..05e66046 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CosmeticsManager.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CosmeticsManager.java @@ -20,16 +20,23 @@ package kr.syeyoung.dungeonsguide.cosmetics; import com.google.gson.JsonPrimitive; import kr.syeyoung.dungeonsguide.DungeonsGuide; +import kr.syeyoung.dungeonsguide.events.PlayerListItemPacketEvent; import kr.syeyoung.dungeonsguide.events.StompConnectedEvent; import kr.syeyoung.dungeonsguide.stomp.*; import kr.syeyoung.dungeonsguide.utils.TextUtils; import lombok.Getter; import net.minecraft.client.Minecraft; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.client.network.NetworkPlayerInfo; +import net.minecraft.network.play.server.S38PacketPlayerListItem; +import net.minecraft.scoreboard.ScorePlayerTeam; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.fml.common.eventhandler.Event; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.ReflectionHelper; import org.json.JSONArray; import org.json.JSONObject; @@ -93,25 +100,41 @@ public class CosmeticsManager implements StompMessageHandler { ActiveCosmetic activeCosmetic = new ActiveCosmetic(); activeCosmetic.setActivityUID(UUID.fromString(jsonObject.getString("activityUID"))); activeCosmetic.setPlayerUID(UUID.fromString(jsonObject.getString("playerUID"))); - activeCosmetic.setCosmeticData(UUID.fromString(jsonObject.getString("cosmeticUID"))); - activeCosmetic.setUsername(jsonObject.getString("username")); + if (jsonObject.isNull("cosmeticUID")) { + ActiveCosmetic activeCosmetic1 = activeCosmeticMap.remove(activeCosmetic.getActivityUID()); - ActiveCosmetic previousThing = activeCosmeticMap.get(activeCosmetic.getActivityUID()); - activeCosmeticMap.put(activeCosmetic.getActivityUID(), activeCosmetic); + List activeCosmetics = activeCosmeticByPlayer.computeIfAbsent(activeCosmetic.getPlayerUID(), a-> new ArrayList<>()); + activeCosmetics.remove(activeCosmetic1); - CosmeticData cosmeticData = cosmeticDataMap.get(activeCosmetic.getCosmeticData()); - if (cosmeticData != null) { - List cosmeticsByTypeList = activeCosmeticByType.computeIfAbsent(cosmeticData.getCosmeticType(), a-> new ArrayList<>()); - cosmeticsByTypeList.add(activeCosmetic); - cosmeticsByTypeList.remove(previousThing); - } - List activeCosmetics = activeCosmeticByPlayer.computeIfAbsent(activeCosmetic.getPlayerUID(), a-> new ArrayList<>()); - activeCosmetics.add(activeCosmetic); - activeCosmetics.remove(previousThing); + activeCosmetics = activeCosmeticByPlayerNameLowerCase.computeIfAbsent(activeCosmetic.getUsername().toLowerCase(), a-> new ArrayList<>()); + activeCosmetics.remove(activeCosmetic1); + + CosmeticData cosmeticData = cosmeticDataMap.get(activeCosmetic.getCosmeticData()); + if (cosmeticData != null) { + List cosmeticsByTypeList = activeCosmeticByType.computeIfAbsent(cosmeticData.getCosmeticType(), a-> new ArrayList<>()); + cosmeticsByTypeList.remove(activeCosmetic1); + } + } else { + activeCosmetic.setCosmeticData(UUID.fromString(jsonObject.getString("cosmeticUID"))); + activeCosmetic.setUsername(jsonObject.getString("username")); + + ActiveCosmetic previousThing = activeCosmeticMap.get(activeCosmetic.getActivityUID()); + activeCosmeticMap.put(activeCosmetic.getActivityUID(), activeCosmetic); + + CosmeticData cosmeticData = cosmeticDataMap.get(activeCosmetic.getCosmeticData()); + if (cosmeticData != null) { + List cosmeticsByTypeList = activeCosmeticByType.computeIfAbsent(cosmeticData.getCosmeticType(), a-> new ArrayList<>()); + cosmeticsByTypeList.add(activeCosmetic); + cosmeticsByTypeList.remove(previousThing); + } + List activeCosmetics = activeCosmeticByPlayer.computeIfAbsent(activeCosmetic.getPlayerUID(), a-> new ArrayList<>()); + activeCosmetics.add(activeCosmetic); + activeCosmetics.remove(previousThing); - activeCosmetics = activeCosmeticByPlayerNameLowerCase.computeIfAbsent(activeCosmetic.getUsername().toLowerCase(), a-> new ArrayList<>()); - activeCosmetics.add(activeCosmetic); - activeCosmetics.remove(previousThing); + activeCosmetics = activeCosmeticByPlayerNameLowerCase.computeIfAbsent(activeCosmetic.getUsername().toLowerCase(), a-> new ArrayList<>()); + activeCosmetics.add(activeCosmetic); + activeCosmetics.remove(previousThing); + } } else if (destination.equals("/user/queue/reply/user.perms")) { @@ -276,13 +299,28 @@ public class CosmeticsManager implements StompMessageHandler { } if (prefix != null) { - preRank += prefix.getData()+" "; + preRank += prefix.getData().replace("&", "§")+" "; } if (color != null) { - last = color.getData() + last; + last = color.getData().replace("&", "§") + last; } } clientChatReceivedEvent.message = new ChatComponentText(preRank + rank + last); } + + + @SubscribeEvent + public void onTabList(PlayerListItemPacketEvent packetPlayerListItem) { + S38PacketPlayerListItem asd = packetPlayerListItem.getPacketPlayerListItem(); + if (asd.getAction() == S38PacketPlayerListItem.Action.ADD_PLAYER) { + if (Minecraft.getMinecraft().getNetHandler() == null) return; + + Map playerInfoMap = ReflectionHelper.getPrivateValue(NetHandlerPlayClient.class, Minecraft.getMinecraft().getNetHandler(), "playerInfoMap", "field_147310_i","i"); + for (S38PacketPlayerListItem.AddPlayerData entry : asd.getEntries()) { + playerInfoMap.remove(entry.getProfile().getId()); + playerInfoMap.put(entry.getProfile().getId(), new CustomNetworkPlayerInfo(entry)); + } + } + } } diff --git a/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomNetworkPlayerInfo.java b/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomNetworkPlayerInfo.java new file mode 100644 index 00000000..ea2cb205 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomNetworkPlayerInfo.java @@ -0,0 +1,65 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2021 cyoung06 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.cosmetics; + +import com.mojang.authlib.GameProfile; +import kr.syeyoung.dungeonsguide.DungeonsGuide; +import kr.syeyoung.dungeonsguide.utils.TextUtils; +import net.minecraft.client.network.NetworkPlayerInfo; +import net.minecraft.network.play.server.S38PacketPlayerListItem; +import net.minecraft.scoreboard.ScorePlayerTeam; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.IChatComponent; + +import java.util.List; + +public class CustomNetworkPlayerInfo extends NetworkPlayerInfo { + public CustomNetworkPlayerInfo(GameProfile p_i46294_1_) { + super(p_i46294_1_); + } + + public CustomNetworkPlayerInfo(S38PacketPlayerListItem.AddPlayerData p_i46295_1_) { + super(p_i46295_1_); + } + + public IChatComponent getDisplayName() + { + String semi_name = super.getDisplayName() != null ? super.getDisplayName().getFormattedText() : ScorePlayerTeam.formatPlayerName(super.getPlayerTeam(), super.getGameProfile().getName()); + + String actualName = ""; + for (String s : semi_name.split(" ")) { + if (TextUtils.stripColor(s).startsWith("[")) continue; + actualName = s; + } + List activeCosmetics = DungeonsGuide.getDungeonsGuide().getCosmeticsManager().getActiveCosmeticByPlayerNameLowerCase().get(TextUtils.stripColor(actualName).toLowerCase()); + + + + if (activeCosmetics == null) return super.getDisplayName(); + CosmeticData color=null; + for (ActiveCosmetic activeCosmetic : activeCosmetics) { + CosmeticData cosmeticData = DungeonsGuide.getDungeonsGuide().getCosmeticsManager().getCosmeticDataMap().get(activeCosmetic.getCosmeticData()); + if (cosmeticData.getCosmeticType().equals("color")) color = cosmeticData; + } + + if (color != null) semi_name = semi_name.replace(actualName, color.getData()+actualName); + + return new ChatComponentText(semi_name); + } +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomPacketPlayerListItem.java b/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomPacketPlayerListItem.java new file mode 100644 index 00000000..2f957c42 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/cosmetics/CustomPacketPlayerListItem.java @@ -0,0 +1,39 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2021 cyoung06 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.cosmetics; + +import kr.syeyoung.dungeonsguide.events.PlayerListItemPacketEvent; +import net.minecraft.network.Packet; +import net.minecraft.network.play.INetHandlerPlayClient; +import net.minecraft.network.play.server.S38PacketPlayerListItem; +import net.minecraftforge.common.MinecraftForge; + +public class CustomPacketPlayerListItem extends S38PacketPlayerListItem { + public CustomPacketPlayerListItem(S38PacketPlayerListItem packet) { + super(packet.getAction()); + getEntries().addAll(packet.getEntries()); + } + + @Override + public void processPacket(INetHandlerPlayClient handler) { + super.processPacket(handler); + + MinecraftForge.EVENT_BUS.post(new PlayerListItemPacketEvent(this)); + } +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/eventlistener/PacketListener.java b/src/main/java/kr/syeyoung/dungeonsguide/eventlistener/PacketListener.java index a05cedce..53eebd7b 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/eventlistener/PacketListener.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/eventlistener/PacketListener.java @@ -24,13 +24,16 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import kr.syeyoung.dungeonsguide.SkyblockStatus; import kr.syeyoung.dungeonsguide.DungeonsGuide; +import kr.syeyoung.dungeonsguide.cosmetics.CustomPacketPlayerListItem; import kr.syeyoung.dungeonsguide.events.PlayerInteractEntityEvent; +import kr.syeyoung.dungeonsguide.events.PlayerListItemPacketEvent; import kr.syeyoung.dungeonsguide.events.TitleEvent; import kr.syeyoung.dungeonsguide.features.FeatureRegistry; import net.minecraft.client.Minecraft; import net.minecraft.network.Packet; import net.minecraft.network.play.client.C02PacketUseEntity; import net.minecraft.network.play.server.S04PacketEntityEquipment; +import net.minecraft.network.play.server.S38PacketPlayerListItem; import net.minecraft.network.play.server.S45PacketTitle; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -55,7 +58,11 @@ public class PacketListener extends ChannelDuplexHandler { if (packet instanceof S45PacketTitle) { MinecraftForge.EVENT_BUS.post(new TitleEvent((S45PacketTitle) packet)); } + if (packet instanceof S38PacketPlayerListItem) { + packet = new CustomPacketPlayerListItem((S38PacketPlayerListItem) packet); + } super.channelRead(ctx, packet); + } @Override diff --git a/src/main/java/kr/syeyoung/dungeonsguide/events/PlayerListItemPacketEvent.java b/src/main/java/kr/syeyoung/dungeonsguide/events/PlayerListItemPacketEvent.java new file mode 100644 index 00000000..65e936f0 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/events/PlayerListItemPacketEvent.java @@ -0,0 +1,30 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2021 cyoung06 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.minecraft.network.play.server.S38PacketPlayerListItem; +import net.minecraftforge.fml.common.eventhandler.Event; + +@Data +@AllArgsConstructor +public class PlayerListItemPacketEvent extends Event { + private S38PacketPlayerListItem packetPlayerListItem; +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknameColor.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknameColor.java index bf91a925..ebfa2456 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknameColor.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknameColor.java @@ -39,7 +39,7 @@ public class FeatureNicknameColor extends SimpleFeature { "§dFrom §r§r§a[RANK§r§6+§r§a] %prefix%%name%§r§7: §r§7TEST§r", "§r§b[RANK§c+§b] %prefix%%name%§f: TEST", "§r§bCo-op > §r§a[RANK§6+§a] %prefix%%name%§f: §rTEST§r" - }, a -> (a+"Color "+(a.equals("§z") ? "(Rainbow on sba)" : "")))); + }, a -> (a.replace("&", "§")+"Color "+(a.replace("&", "§").equals("§z") ? "(Rainbow on sba)" : "")))); return "base." + getKey(); } diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknamePrefix.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknamePrefix.java index 911ee862..e3d5a28f 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknamePrefix.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/FeatureNicknamePrefix.java @@ -46,7 +46,7 @@ public class FeatureNicknamePrefix extends SimpleFeature { "§dFrom §r%prefix% §r§a[RANK§r§6+§r§a] %name%§r§7: §r§7TEST§r", "§r%prefix% §b[RANK§c+§b] %name%§f: TEST", "§r§bCo-op > §r%prefix% §a[RANK§6+§a] %name%§f: §rTEST§r" - }, a->a)); + }, a->a.replace("&", "§"))); return "base." + getKey(); } diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/PrefixSelectorGUI.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/PrefixSelectorGUI.java index 566229e1..b0c2987f 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/PrefixSelectorGUI.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/cosmetics/PrefixSelectorGUI.java @@ -94,7 +94,7 @@ public class PrefixSelectorGUI extends MPanel { GlStateManager.pushMatrix(); GlStateManager.translate(6,17,0); for (int i = 0; i < previews.length; i++) { - fr.drawString(previews[i].replace("%name%", Minecraft.getMinecraft().getSession().getUsername()).replace("%prefix%", prefix), 0, i*fr.FONT_HEIGHT, -1); + fr.drawString(previews[i].replace("%name%", Minecraft.getMinecraft().getSession().getUsername()).replace("%prefix%", prefix.replace("&", "§")), 0, i*fr.FONT_HEIGHT, -1); } GlStateManager.popMatrix(); } diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/playerpreview/FeatureViewPlayerOnJoin.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/playerpreview/FeatureViewPlayerOnJoin.java index 24c25642..2ee94226 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/playerpreview/FeatureViewPlayerOnJoin.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/playerpreview/FeatureViewPlayerOnJoin.java @@ -24,10 +24,13 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture; import io.github.moulberry.hychat.HyChat; import io.github.moulberry.hychat.chat.ChatManager; import io.github.moulberry.hychat.gui.GuiChatBox; +import kr.syeyoung.dungeonsguide.DungeonsGuide; import kr.syeyoung.dungeonsguide.config.guiconfig.ConfigPanelCreator; import kr.syeyoung.dungeonsguide.config.guiconfig.FeatureEditPane; import kr.syeyoung.dungeonsguide.config.guiconfig.GuiConfig; import kr.syeyoung.dungeonsguide.config.guiconfig.PanelDefaultParameterConfig; +import kr.syeyoung.dungeonsguide.cosmetics.ActiveCosmetic; +import kr.syeyoung.dungeonsguide.cosmetics.CosmeticData; import kr.syeyoung.dungeonsguide.features.FeatureParameter; import kr.syeyoung.dungeonsguide.features.FeatureRegistry; import kr.syeyoung.dungeonsguide.features.SimpleFeature; @@ -245,7 +248,23 @@ public class FeatureViewPlayerOnJoin extends SimpleFeature implements GuiPostRen GlStateManager.color(1, 1, 1, 1.0F); if (fakePlayer != null) { GuiInventory.drawEntityOnScreen(45, 150, 60, -(mouseX - popupRect.x - 75), 0, fakePlayer); - fr.drawString(fakePlayer.getName(), (90 - fr.getStringWidth(fakePlayer.getName())) / 2, 15, 0xFFEFFF00); + + String toDraw = fakePlayer.getName(); + List activeCosmetics = DungeonsGuide.getDungeonsGuide().getCosmeticsManager().getActiveCosmeticByPlayer().get(UUID.fromString(TextUtils.insertDashUUID(playerProfile.get().getMemberUID()))); + CosmeticData prefix = null, color = null; + if (activeCosmetics != null) { + for (ActiveCosmetic activeCosmetic : activeCosmetics) { + CosmeticData cosmeticData = DungeonsGuide.getDungeonsGuide().getCosmeticsManager().getCosmeticDataMap().get(activeCosmetic.getCosmeticData()); + if (cosmeticData != null) { + if (cosmeticData.getCosmeticType().equals("prefix")) prefix = cosmeticData; + if (cosmeticData.getCosmeticType().equals("color")) color = cosmeticData; + } + } + } + toDraw = (color == null ? "§e" : color.getData().replace("&", "§"))+toDraw; + if (prefix != null) toDraw = prefix.getData().replace("&", "§") + " "+toDraw; + + fr.drawString(toDraw, (90 - fr.getStringWidth(toDraw)) / 2, 15, -1); ItemStack toHover = null; if (relX > 20 && relX < 70) { -- cgit