diff --git a/src/main/java/com/roxiun/mellow/feature/stats/tab/ExtendedStatsTabOverlay.java b/src/main/java/com/roxiun/mellow/feature/stats/tab/ExtendedStatsTabOverlay.java index af2bc33..57582b2 100644 --- a/src/main/java/com/roxiun/mellow/feature/stats/tab/ExtendedStatsTabOverlay.java +++ b/src/main/java/com/roxiun/mellow/feature/stats/tab/ExtendedStatsTabOverlay.java @@ -8,12 +8,13 @@ import com.roxiun.mellow.api.provider.model.StatScope; import com.roxiun.mellow.api.seraph.SeraphClientType; import com.roxiun.mellow.api.seraph.SeraphTag; -import com.roxiun.mellow.api.urchin.UrchinTag; import com.roxiun.mellow.config.MellowOneConfig; import com.roxiun.mellow.data.TabStats; import com.roxiun.mellow.util.formatting.FormattingUtils; import com.roxiun.mellow.util.player.PlayerUtils; import com.roxiun.mellow.util.render.SeraphClientIconRenderer; +import com.roxiun.mellow.util.render.SeraphTagIconRenderer; +import com.roxiun.mellow.util.render.UrchinTagIconRenderer; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -50,6 +51,9 @@ public class ExtendedStatsTabOverlay extends GuiPlayerTabOverlay { private static final int HEAD_TEXT_GAP = 2; private static final int TEAM_COLLAPSED_GAP = 1; private static final int CLIENT_ICON_SIZE = ENTRY_HEIGHT - 1; + private static final int TAG_ICON_SIZE = 8; + private static final int TAG_ICON_GAP = 1; + private static final int TAG_TEXT_GAP = 2; private final Minecraft mc; private final MellowOneConfig config; @@ -396,6 +400,7 @@ private void drawValues( int width = columnWidths.get(i); int textStartX = x + CELL_PADDING_X; int reservedLeft = CELL_PADDING_X * 2; + TabStats stats = getStatsForInfo(info, scope); if (column == 2 && shouldShowHeadsInExtendedView()) { int headX = x + CELL_PADDING_X; @@ -405,8 +410,6 @@ private void drawValues( reservedLeft += HEAD_ICON_SIZE + HEAD_TEXT_GAP; } - int maxTextWidth = Math.max(1, width - reservedLeft); - if (ExtendedTabStatsColumns.isClientColumn(scope, column)) { drawClientIcon(info, x, width, baselineY); x += width; @@ -416,11 +419,86 @@ private void drawValues( continue; } + if (ExtendedTabStatsColumns.isTagsColumn(scope, column)) { + int urchinIconWidth = getUrchinTagIconWidth(stats); + int seraphIconWidth = getSeraphTagIconWidth(stats); + int totalIconWidth = urchinIconWidth + seraphIconWidth; + if (urchinIconWidth > 0 && seraphIconWidth > 0) { + totalIconWidth += TAG_ICON_GAP; + } + + String value = fitToWidth( + getDisplayValue(info, column, scope, i), + Math.max( + 1, + width - + reservedLeft - + totalIconWidth - + (totalIconWidth > 0 && !getDisplayValue(info, column, scope, i).isEmpty() + ? TAG_TEXT_GAP + : 0) + ) + ); + int totalWidth = mc.fontRendererObj.getStringWidth(value); + if (totalIconWidth > 0) { + totalWidth += totalIconWidth; + if (!value.isEmpty()) { + totalWidth += TAG_TEXT_GAP; + } + } + + int drawX = isCenterAlignedColumn(scope, column) + ? x + (width - totalWidth) / 2 + : textStartX; + + if (!value.isEmpty()) { + mc.fontRendererObj.drawStringWithShadow(value, drawX, baselineY, -1); + } + if (totalIconWidth > 0) { + int iconX = drawX + mc.fontRendererObj.getStringWidth(value); + if (!value.isEmpty()) { + iconX += TAG_TEXT_GAP; + } + int centerY = baselineY + (mc.fontRendererObj.FONT_HEIGHT - TAG_ICON_SIZE) / 2; + + if (urchinIconWidth > 0) { + drawUrchinTagIcons(stats, iconX, centerY); + iconX += urchinIconWidth; + } + if (seraphIconWidth > 0) { + if (urchinIconWidth > 0) { + iconX += TAG_ICON_GAP; + } + drawSeraphTagIcons(stats, iconX, centerY); + } + } + + x += width; + if (i < columns.size() - 1) { + x += getGapAfterColumn(columns, i); + } + continue; + } + + int maxTextWidth = Math.max(1, width - reservedLeft); + if (column == 2 && shouldKeepTagsInName(scope)) { + int urchinIconWidth = getUrchinTagIconWidth(stats); + int seraphIconWidth = getSeraphTagIconWidth(stats); + int totalIconWidth = urchinIconWidth + seraphIconWidth; + if (urchinIconWidth > 0 && seraphIconWidth > 0) { + totalIconWidth += TAG_ICON_GAP; + } + maxTextWidth = Math.max( + 1, + maxTextWidth - totalIconWidth - (totalIconWidth > 0 ? TAG_TEXT_GAP : 0) + ); + } + String value = fitToWidth( getDisplayValue(info, column, scope, i), maxTextWidth ); - if (value != null && !value.isEmpty()) { + if (!value.isEmpty()) { int drawX; if (isCenterAlignedColumn(scope, column)) { drawX = x + (width - mc.fontRendererObj.getStringWidth(value)) / 2; @@ -434,6 +512,33 @@ private void drawValues( drawX = textStartX; } mc.fontRendererObj.drawStringWithShadow(value, drawX, baselineY, -1); + + if (column == 2 && shouldKeepTagsInName(scope)) { + int urchinIconWidth = getUrchinTagIconWidth(stats); + int seraphIconWidth = getSeraphTagIconWidth(stats); + int totalIconWidth = urchinIconWidth + seraphIconWidth; + if (urchinIconWidth > 0 && seraphIconWidth > 0) { + totalIconWidth += TAG_ICON_GAP; + } + if (totalIconWidth > 0) { + int iconX = drawX + mc.fontRendererObj.getStringWidth(value); + if (!value.isEmpty()) { + iconX += TAG_TEXT_GAP; + } + int centerY = baselineY + (mc.fontRendererObj.FONT_HEIGHT - TAG_ICON_SIZE) / 2; + + if (urchinIconWidth > 0) { + drawUrchinTagIcons(stats, iconX, centerY); + iconX += urchinIconWidth; + } + if (seraphIconWidth > 0) { + if (urchinIconWidth > 0) { + iconX += TAG_ICON_GAP; + } + drawSeraphTagIcons(stats, iconX, centerY); + } + } + } } x += width; if (i < columns.size() - 1) { @@ -451,9 +556,26 @@ private int getCellContentWidth( if (ExtendedTabStatsColumns.isClientColumn(scope, column)) { return getCachedClientType(info) == null ? 0 : CLIENT_ICON_SIZE; } - return mc.fontRendererObj.getStringWidth( - getDisplayValue(info, column, scope, columnIndex) - ); + + TabStats stats = getStatsForInfo(info, scope); + String value = getDisplayValue(info, column, scope, columnIndex); + int width = mc.fontRendererObj.getStringWidth(value); + int urchinIconWidth = getUrchinTagIconWidth(stats); + int seraphIconWidth = getSeraphTagIconWidth(stats); + int totalIconWidth = urchinIconWidth + seraphIconWidth; + if (urchinIconWidth > 0 && seraphIconWidth > 0) { + totalIconWidth += TAG_ICON_GAP; + } + + if (ExtendedTabStatsColumns.isTagsColumn(scope, column)) { + return width + totalIconWidth + (totalIconWidth > 0 && !value.isEmpty() ? TAG_TEXT_GAP : 0); + } + + if (column == 2 && shouldKeepTagsInName(scope)) { + return width + totalIconWidth + (totalIconWidth > 0 && !value.isEmpty() ? TAG_TEXT_GAP : 0); + } + + return width; } private String fitToWidth(String value, int width) { @@ -1207,21 +1329,15 @@ private String appendTagSuffixes(String value, TabStats stats) { return value; } - String safe = value == null ? "" : value; - - if (Mellow.config.showUrchinTagsInTab && stats.isUrchinTagged()) { - for (UrchinTag tag : stats.getUrchinTags()) { - safe += " " + FormattingUtils.formatUrchinTagIcon(tag); - } - } + StringBuilder safe = new StringBuilder(value == null ? "" : value); if (Mellow.config.showSeraphTagsInTab && stats.isSeraphTagged()) { for (SeraphTag tag : stats.getSeraphTags()) { - safe += " " + FormattingUtils.formatSeraphTagIcon(tag); + safe.append(' ').append(FormattingUtils.formatSeraphTagIcon(tag)); } } - return safe; + return safe.toString(); } private int getRowBackground(NetworkPlayerInfo info) { @@ -1290,27 +1406,103 @@ private String buildTagsColumnValue(NetworkPlayerInfo info, TabStats stats) { } if (stats != null && Mellow.config != null) { - if (Mellow.config.showUrchinTagsInTab && stats.isUrchinTagged()) { - for (UrchinTag tag : stats.getUrchinTags()) { - if (builder.length() > 0) { - builder.append(" "); - } - builder.append(FormattingUtils.formatUrchinTagIcon(tag)); - } - } - if (Mellow.config.showSeraphTagsInTab && stats.isSeraphTagged()) { - for (SeraphTag tag : stats.getSeraphTags()) { - if (builder.length() > 0) { - builder.append(" "); - } - builder.append(FormattingUtils.formatSeraphTagIcon(tag)); - } - } + // Seraph tags are rendered as icons in drawValues(), not as text badges } return builder.toString(); } + private TabStats getStatsForInfo(NetworkPlayerInfo info, StatScope scope) { + if (info == null || info.getGameProfile() == null) { + return null; + } + + String playerName = info.getGameProfile().getName(); + if (playerName == null || playerName.isEmpty()) { + return null; + } + + TabStats stats = Mellow.tabStats.get(playerName); + boolean isNicked = + Mellow.nickUtils != null && Mellow.nickUtils.isNicked(playerName); + if (stats == null && isNicked && Mellow.nickUtils != null) { + stats = Mellow.nickUtils.getResolvedTabStatsForNick(playerName, scope); + } + return stats; + } + + private int getUrchinTagIconWidth(TabStats stats) { + if ( + stats == null || + Mellow.config == null || + !Mellow.config.showUrchinTagsInTab || + !stats.isUrchinTagged() + ) { + return 0; + } + + return UrchinTagIconRenderer.measureTags( + stats.getUrchinTags(), + TAG_ICON_SIZE, + TAG_ICON_GAP + ); + } + + private void drawUrchinTagIcons(TabStats stats, int x, int y) { + if ( + stats == null || + Mellow.config == null || + !Mellow.config.showUrchinTagsInTab || + !stats.isUrchinTagged() + ) { + return; + } + + UrchinTagIconRenderer.drawTags( + stats.getUrchinTags(), + x, + y, + TAG_ICON_SIZE, + TAG_ICON_GAP + ); + } + + private int getSeraphTagIconWidth(TabStats stats) { + if ( + stats == null || + Mellow.config == null || + !Mellow.config.showSeraphTagsInTab || + !stats.isSeraphTagged() + ) { + return 0; + } + + return SeraphTagIconRenderer.measureTags( + stats.getSeraphTags(), + TAG_ICON_SIZE, + TAG_ICON_GAP + ); + } + + private void drawSeraphTagIcons(TabStats stats, int x, int y) { + if ( + stats == null || + Mellow.config == null || + !Mellow.config.showSeraphTagsInTab || + !stats.isSeraphTagged() + ) { + return; + } + + SeraphTagIconRenderer.drawTags( + stats.getSeraphTags(), + x, + y, + TAG_ICON_SIZE, + TAG_ICON_GAP + ); + } + private String buildPingColumnValue(NetworkPlayerInfo info) { if ( Mellow.config == null || diff --git a/src/main/java/com/roxiun/mellow/util/render/SeraphTagIconRenderer.java b/src/main/java/com/roxiun/mellow/util/render/SeraphTagIconRenderer.java new file mode 100644 index 0000000..817fa7b --- /dev/null +++ b/src/main/java/com/roxiun/mellow/util/render/SeraphTagIconRenderer.java @@ -0,0 +1,144 @@ +package com.roxiun.mellow.util.render; + +import com.roxiun.mellow.api.seraph.SeraphTag; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; + +public final class SeraphTagIconRenderer { + + private static final int SOURCE_SIZE = 56; + private static final ResourceLocation SNIPER = new ResourceLocation( + "mellow", "textures/tags/seraph/sniper.png" + ); + private static final ResourceLocation BLATANT_CHEATING = new ResourceLocation( + "mellow", "textures/tags/seraph/blatant_cheating.png" + ); + private static final ResourceLocation LEGIT_SNIPER = new ResourceLocation( + "mellow", "textures/tags/seraph/legit_sniper.png" + ); + private static final ResourceLocation BOT = new ResourceLocation( + "mellow", "textures/tags/seraph/bot.png" + ); + private static final ResourceLocation ALT = new ResourceLocation( + "mellow", "textures/tags/seraph/alt.png" + ); + private static final ResourceLocation ANNOYING = new ResourceLocation( + "mellow", "textures/tags/seraph/annoying.png" + ); + private static final ResourceLocation CAUTION = new ResourceLocation( + "mellow", "textures/tags/seraph/caution.png" + ); + private static final ResourceLocation CLOSET_CHEATING = new ResourceLocation( + "mellow", "textures/tags/seraph/closet_cheating.png" + ); + private static final ResourceLocation POTENTIAL_SNIPER = new ResourceLocation( + "mellow", "textures/tags/seraph/potential_sniper.png" + ); + + private SeraphTagIconRenderer() {} + + public static int drawTags(List tags, int x, int y, int size, int gap) { + if (tags == null || tags.isEmpty() || size <= 0) { + return 0; + } + + int drawX = x; + int drawn = 0; + for (SeraphTag tag : tags) { + if (tag == null) { + continue; + } + ResourceLocation texture = getTexture(tag.getTagName()); + if (texture == null) { + continue; + } + + drawIcon(texture, drawX, y, size); + drawX += size + gap; + drawn += size + gap; + } + + return drawn > 0 ? drawn - gap : 0; + } + + public static int measureTags(List tags, int size, int gap) { + if (tags == null || tags.isEmpty() || size <= 0) { + return 0; + } + + int count = 0; + for (SeraphTag tag : tags) { + if (tag != null && getTexture(tag.getTagName()) != null) { + count++; + } + } + if (count == 0) { + return 0; + } + return (count * size) + ((count - 1) * gap); + } + + private static void drawIcon(ResourceLocation texture, int x, int y, int size) { + if (texture == null || size <= 0) { + return; + } + + Minecraft mc = Minecraft.getMinecraft(); + if (mc == null || mc.getTextureManager() == null) { + return; + } + + mc.getTextureManager().bindTexture(texture); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + Gui.drawScaledCustomSizeModalRect( + x, + y, + 0.0F, + 0.0F, + SOURCE_SIZE, + SOURCE_SIZE, + size, + size, + SOURCE_SIZE, + SOURCE_SIZE + ); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + } + + private static ResourceLocation getTexture(String tagName) { + if (tagName == null || tagName.trim().isEmpty()) { + return null; + } + + switch (tagName.toLowerCase()) { + case "seraph.sniping": + return SNIPER; + case "seraph.blatant_cheating": + return BLATANT_CHEATING; + case "seraph.legit_sniping": + return LEGIT_SNIPER; + case "seraph.bot": + return BOT; + case "seraph.alt": + return ALT; + case "seraph.annoylist": + return ANNOYING; + case "seraph.caution": + return CAUTION; + case "seraph.closet_cheating": + return CLOSET_CHEATING; + case "seraph.potential_sniper": + return POTENTIAL_SNIPER; + // Tags without textures: potential_sniper, safelist.*, encounters, cookie, verified + default: + return null; + } + } +} + diff --git a/src/main/java/com/roxiun/mellow/util/render/UrchinTagIconRenderer.java b/src/main/java/com/roxiun/mellow/util/render/UrchinTagIconRenderer.java new file mode 100644 index 0000000..70776d3 --- /dev/null +++ b/src/main/java/com/roxiun/mellow/util/render/UrchinTagIconRenderer.java @@ -0,0 +1,143 @@ +package com.roxiun.mellow.util.render; + +import com.roxiun.mellow.api.urchin.UrchinTag; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; + +public final class UrchinTagIconRenderer { + + private static final int SOURCE_SIZE = 56; + private static final ResourceLocation SNIPER = new ResourceLocation( + "mellow", "textures/tags/urchin/sniper.png" + ); + private static final ResourceLocation BLATANT_CHEATER = new ResourceLocation( + "mellow", "textures/tags/urchin/blatant_cheater.png" + ); + private static final ResourceLocation CONFIRMED_CHEATER = new ResourceLocation( + "mellow", "textures/tags/urchin/confirmed_cheater.png" + ); + private static final ResourceLocation CLOSET_CHEATER = new ResourceLocation( + "mellow", "textures/tags/urchin/closet_cheater.png" + ); + private static final ResourceLocation POSSIBLE_SNIPER = new ResourceLocation( + "mellow", "textures/tags/urchin/potential_sniper.png" + ); + private static final ResourceLocation LEGIT_SNIPER = new ResourceLocation( + "mellow", "textures/tags/urchin/legit_sniper.png" + ); + private static final ResourceLocation CAUTION = new ResourceLocation( + "mellow", "textures/tags/urchin/caution.png" + ); + private static final ResourceLocation ACCOUNT = new ResourceLocation( + "mellow", "textures/tags/urchin/account.png" + ); + private static final ResourceLocation INFO = new ResourceLocation( + "mellow", "textures/tags/urchin/info.png" + ); + + private UrchinTagIconRenderer() {} + + public static int drawTags(List tags, int x, int y, int size, int gap) { + if (tags == null || tags.isEmpty() || size <= 0) { + return 0; + } + + int drawX = x; + int drawn = 0; + for (UrchinTag tag : tags) { + if (tag == null) { + continue; + } + ResourceLocation texture = getTexture(tag.getType()); + if (texture == null) { + continue; + } + + drawIcon(texture, drawX, y, size); + drawX += size + gap; + drawn += size + gap; + } + + return drawn > 0 ? drawn - gap : 0; + } + + public static int measureTags(List tags, int size, int gap) { + if (tags == null || tags.isEmpty() || size <= 0) { + return 0; + } + + int count = 0; + for (UrchinTag tag : tags) { + if (tag != null && getTexture(tag.getType()) != null) { + count++; + } + } + if (count == 0) { + return 0; + } + return (count * size) + ((count - 1) * gap); + } + + private static void drawIcon(ResourceLocation texture, int x, int y, int size) { + if (texture == null || size <= 0) { + return; + } + + Minecraft mc = Minecraft.getMinecraft(); + if (mc == null || mc.getTextureManager() == null) { + return; + } + + mc.getTextureManager().bindTexture(texture); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + Gui.drawScaledCustomSizeModalRect( + x, + y, + 0.0F, + 0.0F, + SOURCE_SIZE, + SOURCE_SIZE, + size, + size, + SOURCE_SIZE, + SOURCE_SIZE + ); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + } + + private static ResourceLocation getTexture(String type) { + if (type == null || type.trim().isEmpty()) { + return null; + } + + switch (type.toLowerCase()) { + case "sniper": + return SNIPER; + case "blatant_cheater": + return BLATANT_CHEATER; + case "confirmed_cheater": + return CONFIRMED_CHEATER; + case "closet_cheater": + return CLOSET_CHEATER; + case "possible_sniper": + return POSSIBLE_SNIPER; + case "legit_sniper": + return LEGIT_SNIPER; + case "caution": + return CAUTION; + case "account": + return ACCOUNT; + case "info": + return INFO; + default: + return null; + } + } +} + diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/alt.png b/src/main/resources/assets/mellow/textures/tags/seraph/alt.png new file mode 100644 index 0000000..8a055af Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/alt.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/annoying.png b/src/main/resources/assets/mellow/textures/tags/seraph/annoying.png new file mode 100644 index 0000000..28f1c2f Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/annoying.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/blatant_cheating.png b/src/main/resources/assets/mellow/textures/tags/seraph/blatant_cheating.png new file mode 100644 index 0000000..6a868bf Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/blatant_cheating.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/bot.png b/src/main/resources/assets/mellow/textures/tags/seraph/bot.png new file mode 100644 index 0000000..5fd7423 Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/bot.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/caution.png b/src/main/resources/assets/mellow/textures/tags/seraph/caution.png new file mode 100644 index 0000000..3c9a5a8 Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/caution.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/closet_cheating.png b/src/main/resources/assets/mellow/textures/tags/seraph/closet_cheating.png new file mode 100644 index 0000000..6d98d6c Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/closet_cheating.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/legit_sniper.png b/src/main/resources/assets/mellow/textures/tags/seraph/legit_sniper.png new file mode 100644 index 0000000..f519695 Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/legit_sniper.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/potential_sniper.png b/src/main/resources/assets/mellow/textures/tags/seraph/potential_sniper.png new file mode 100644 index 0000000..5d22385 Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/potential_sniper.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/seraph/sniper.png b/src/main/resources/assets/mellow/textures/tags/seraph/sniper.png new file mode 100644 index 0000000..16cfa1b Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/seraph/sniper.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/account.png b/src/main/resources/assets/mellow/textures/tags/urchin/account.png new file mode 100644 index 0000000..2cd3fbb Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/account.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/blatant_cheater.png b/src/main/resources/assets/mellow/textures/tags/urchin/blatant_cheater.png new file mode 100644 index 0000000..632a25d Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/blatant_cheater.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/caution.png b/src/main/resources/assets/mellow/textures/tags/urchin/caution.png new file mode 100644 index 0000000..ec9b6af Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/caution.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/closet_cheater.png b/src/main/resources/assets/mellow/textures/tags/urchin/closet_cheater.png new file mode 100644 index 0000000..c13897e Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/closet_cheater.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/confirmed_cheater.png b/src/main/resources/assets/mellow/textures/tags/urchin/confirmed_cheater.png new file mode 100644 index 0000000..0d9b20e Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/confirmed_cheater.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/info.png b/src/main/resources/assets/mellow/textures/tags/urchin/info.png new file mode 100644 index 0000000..a0a1826 Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/info.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/legit_sniper.png b/src/main/resources/assets/mellow/textures/tags/urchin/legit_sniper.png new file mode 100644 index 0000000..7048bbc Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/legit_sniper.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/possible_sniper.png b/src/main/resources/assets/mellow/textures/tags/urchin/possible_sniper.png new file mode 100644 index 0000000..474bbc6 Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/possible_sniper.png differ diff --git a/src/main/resources/assets/mellow/textures/tags/urchin/sniper.png b/src/main/resources/assets/mellow/textures/tags/urchin/sniper.png new file mode 100644 index 0000000..a29d6b5 Binary files /dev/null and b/src/main/resources/assets/mellow/textures/tags/urchin/sniper.png differ