From 7a0da0e7f0522317613aba4942b15ee5cb6ffbb5 Mon Sep 17 00:00:00 2001 From: Good627 <193092356+Good627@users.noreply.github.com> Date: Sun, 25 Oct 2026 22:08:46 +0300 Subject: [PATCH 1/5] feat(party): Guild and ally party finder revamp with spacious cards and validation tooltips --- .../edenmod/textures/gui/icons/festival.png | Bin 0 -> 21029 bytes src/tel/eden/mod/EdenModClient.java | 56 +++- src/tel/eden/mod/chat/PartyFormatter.java | 28 +- src/tel/eden/mod/gui/EdenMenuScreen.java | 13 +- src/tel/eden/mod/gui/PartyCreateScreen.java | 307 ++++++++++------- src/tel/eden/mod/gui/PartyListScreen.java | 159 +++++++-- src/tel/eden/mod/gui/PartyManageScreen.java | 315 ++++++++++++------ .../eden/mod/net/BridgeWebSocketClient.java | 47 ++- src/tel/eden/mod/net/PartyInfo.java | 32 +- 9 files changed, 707 insertions(+), 250 deletions(-) create mode 100644 resources/assets/edenmod/textures/gui/icons/festival.png diff --git a/resources/assets/edenmod/textures/gui/icons/festival.png b/resources/assets/edenmod/textures/gui/icons/festival.png new file mode 100644 index 0000000000000000000000000000000000000000..97005d754e83aa48493c5383d670d2f5879c2230 GIT binary patch literal 21029 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 literal 0 HcmV?d00001 diff --git a/src/tel/eden/mod/EdenModClient.java b/src/tel/eden/mod/EdenModClient.java index 4775d4c..0277127 100644 --- a/src/tel/eden/mod/EdenModClient.java +++ b/src/tel/eden/mod/EdenModClient.java @@ -414,6 +414,35 @@ public void openCenteredEmotePicker() { }); } + private volatile String currentWynnWorld = ""; + private volatile boolean festivalEnabled = false; + private volatile boolean annihilationActive = false; + + public String currentWynnWorld() { + return currentWynnWorld; + } + + public boolean isFestivalEnabled() { + return festivalEnabled; + } + + public boolean isAnnihilationActive() { + return annihilationActive; + } + + public PartyInfo activeParty() { + String ign = playerName(); + if (ign == null || ign.isBlank()) { + return null; + } + for (PartyInfo party : knownParties) { + if (party.host().equalsIgnoreCase(ign) || party.members().stream().anyMatch(m -> m.equalsIgnoreCase(ign))) { + return party; + } + } + return null; + } + /** An immutable snapshot of the currently known parties, safe to read off-thread. */ public java.util.List knownParties() { return knownParties; @@ -1134,6 +1163,12 @@ public void onPartyUpdate(String event, String actor, PartyInfo party, String co } } + @Override + public void onPartyConfig(boolean festivalEnabled, boolean annihilationActive) { + EdenModClient.this.festivalEnabled = festivalEnabled; + EdenModClient.this.annihilationActive = annihilationActive; + } + @Override public void onPartyList(java.util.List parties, String color) { replaceKnownParties(parties); @@ -2588,14 +2623,27 @@ private void setInGameWorld(boolean active) { /** Returns true when the Wynncraft tab list is visible (game world or /class screen). */ private boolean checkWynncraftTabActive(Minecraft mc) { net.minecraft.client.multiplayer.ClientPacketListener conn = mc.getConnection(); - if (conn == null) + if (conn == null) { + currentWynnWorld = ""; return false; + } for (net.minecraft.client.multiplayer.PlayerInfo info : conn.getOnlinePlayers()) { net.minecraft.network.chat.Component name = info.getTabListDisplayName(); - if (name != null && (name.getString().contains("Global [") || name.getString().contains("Island Info"))) { - return true; + if (name != null) { + String str = name.getString(); + if (str.contains("Global [") || str.contains("Island Info")) { + if (str.contains("Global [")) { + int start = str.indexOf("Global [") + 8; + int end = str.indexOf(']', start); + if (end > start) { + currentWynnWorld = str.substring(start, end).trim(); + } + } + return true; + } } } + currentWynnWorld = ""; return false; } @@ -2690,6 +2738,7 @@ private synchronized void disconnect() { partyCommandGeneration.incrementAndGet(); onWynncraft = false; loginPending = false; + currentWynnWorld = ""; bridgeStatus = BridgeStatus.UNKNOWN; liveGuildRank = ""; liveDiscordRank = ""; @@ -2854,6 +2903,7 @@ public void handleSystemChat(Component message) { // Annihilation world-event warning (the only advance timing signal we get). java.util.OptionalInt anni = AnnihilationParser.parse(message); if (anni.isPresent()) { + annihilationActive = true; int seconds = anni.getAsInt(); if (annihilationRelay.shouldSend(new CapturedMessage("annihilation", null, String.valueOf(seconds)))) { current.sendAnnihilation(seconds); diff --git a/src/tel/eden/mod/chat/PartyFormatter.java b/src/tel/eden/mod/chat/PartyFormatter.java index 81c3336..59703d0 100644 --- a/src/tel/eden/mod/chat/PartyFormatter.java +++ b/src/tel/eden/mod/chat/PartyFormatter.java @@ -22,7 +22,15 @@ private PartyFormatter() { /** " wants to raid (n/max) [JOIN #id]" with the open note, if any. */ public static Component partyOpen(PartyInfo party) { - MutableComponent body = Component.empty().append(Component.literal(party.host()).withStyle(ChatFormatting.AQUA)).append(Component.literal(" wants to raid ").withStyle(ChatFormatting.GREEN)).append(Component.literal(party.raid()).withStyle(ChatFormatting.YELLOW)).append(Component.literal(" (" + party.size() + "/" + party.max() + ") ").withStyle(ChatFormatting.GRAY)).append(joinButton(party.id())); + MutableComponent body = Component.empty(); + if (party.ally()) { + body.append(Component.literal("[Ally] ").withStyle(ChatFormatting.LIGHT_PURPLE)); + } + body.append(Component.literal(party.host()).withStyle(ChatFormatting.AQUA)).append(Component.literal(" wants to raid ").withStyle(ChatFormatting.GREEN)).append(Component.literal(party.raid()).withStyle(ChatFormatting.YELLOW)).append(Component.literal(" (" + party.size() + "/" + party.max() + ") ").withStyle(ChatFormatting.GRAY)); + if (party.world() != null && !party.world().isBlank()) { + body.append(Component.literal("[" + party.world() + "] ").withStyle(ChatFormatting.DARK_AQUA)); + } + body.append(joinButton(party.id())); if (!party.note().isBlank()) { body.append(Component.literal(" — ").withStyle(ChatFormatting.GRAY)).append(Component.literal("\"" + party.note() + "\"").withStyle(Style.EMPTY.withColor(ChatFormatting.WHITE).withItalic(true))); } @@ -43,7 +51,12 @@ public static Component partyLeave(String actor, PartyInfo party) { /** "Party #id () is full! [Create party]" (the button shows for the host). */ public static Component partyFull(PartyInfo party) { - MutableComponent body = Component.empty().append(Component.literal("Party #" + party.id() + " ").withStyle(ChatFormatting.GOLD)).append(Component.literal("(" + party.raid() + ") ").withStyle(ChatFormatting.YELLOW)).append(Component.literal("is full! ").withStyle(ChatFormatting.GREEN)).append(Component.literal(String.join(", ", party.members())).withStyle(ChatFormatting.GRAY)); + MutableComponent body = Component.empty(); + if (party.ally()) { + body.append(Component.literal("[Ally] ").withStyle(ChatFormatting.LIGHT_PURPLE)); + } + List realMembers = party.members().stream().filter(m -> !m.equals("*filled*")).toList(); + body.append(Component.literal("Party #" + party.id() + " ").withStyle(ChatFormatting.GOLD)).append(Component.literal("(" + party.raid() + ") ").withStyle(ChatFormatting.YELLOW)).append(Component.literal("is full! ").withStyle(ChatFormatting.GREEN)).append(Component.literal(String.join(", ", realMembers)).withStyle(ChatFormatting.GRAY)); // Offer the host a one-click "make the in-game party" prompt. String self = localName(); if (self != null && self.equalsIgnoreCase(party.host()) && !party.members().isEmpty()) { @@ -54,7 +67,8 @@ public static Component partyFull(PartyInfo party) { /** Clickable that runs {@code /eden party makeingame } to form the in-game party. */ private static Component createButton(PartyInfo party) { - Style style = Style.EMPTY.withColor(ChatFormatting.GOLD).withUnderlined(true).withClickEvent(new ClickEvent.RunCommand("/eden party makeingame " + String.join(" ", party.members()))).withHoverEvent(new HoverEvent.ShowText(Component.literal("Click to create the in-game party and invite everyone"))); + List realMembers = party.members().stream().filter(m -> !m.equals("*filled*")).toList(); + Style style = Style.EMPTY.withColor(ChatFormatting.GOLD).withUnderlined(true).withClickEvent(new ClickEvent.RunCommand("/eden party makeingame " + String.join(" ", realMembers))).withHoverEvent(new HoverEvent.ShowText(Component.literal("Click to create the in-game party and invite everyone"))); return Component.literal("[Create party]").setStyle(style); } @@ -71,8 +85,16 @@ public static Component listing(List parties) { MutableComponent out = line(Component.literal("Open Eden parties (" + parties.size() + "):").withStyle(ChatFormatting.GREEN)); for (PartyInfo party : parties) { out.append("\n"); + if (party.ally()) { + out.append(Component.literal("[Ally] ").withStyle(ChatFormatting.LIGHT_PURPLE)); + } else { + out.append(Component.literal("[Guild] ").withStyle(ChatFormatting.DARK_GREEN)); + } out.append(Component.literal(" " + party.raid() + " ").withStyle(ChatFormatting.YELLOW)); out.append(Component.literal(party.size() + "/" + party.max() + " | ").withStyle(ChatFormatting.GRAY)); + if (party.world() != null && !party.world().isBlank()) { + out.append(Component.literal("[" + party.world() + "] ").withStyle(ChatFormatting.DARK_AQUA)); + } out.append(Component.literal(party.host() + " ").withStyle(ChatFormatting.AQUA)); out.append(joinButton(party.id())); if (!party.note().isBlank()) { diff --git a/src/tel/eden/mod/gui/EdenMenuScreen.java b/src/tel/eden/mod/gui/EdenMenuScreen.java index 4da206e..304bba1 100644 --- a/src/tel/eden/mod/gui/EdenMenuScreen.java +++ b/src/tel/eden/mod/gui/EdenMenuScreen.java @@ -60,18 +60,11 @@ protected void init() { }).bounds(metrics.buttonX, metrics.startY + metrics.buttonPitch, metrics.buttonWidth, metrics.buttonHeight).build()); this.addRenderableWidget(Button.builder(Component.literal("Manage Party"), button -> { - String ign = EdenModClient.instance().playerName(); - tel.eden.mod.net.PartyInfo myParty = null; - for (tel.eden.mod.net.PartyInfo p : EdenModClient.instance().knownParties()) { - if (ign != null && p.host().equalsIgnoreCase(ign)) { - myParty = p; - break; - } - } + tel.eden.mod.net.PartyInfo myParty = EdenModClient.instance() != null ? EdenModClient.instance().activeParty() : null; if (myParty != null) { this.minecraft.setScreen(new PartyManageScreen(this, EdenModClient.instance(), myParty)); - } else { - this.minecraft.player.displayClientMessage(Component.literal("You are not hosting a party!").withStyle(net.minecraft.ChatFormatting.RED), false); + } else if (this.minecraft.player != null) { + this.minecraft.player.displayClientMessage(Component.literal("You are not in an active party!").withStyle(net.minecraft.ChatFormatting.RED), false); } }).bounds(metrics.buttonX, metrics.startY + (metrics.buttonPitch * 2), metrics.buttonWidth, metrics.buttonHeight).build()); diff --git a/src/tel/eden/mod/gui/PartyCreateScreen.java b/src/tel/eden/mod/gui/PartyCreateScreen.java index a224024..2259c5a 100644 --- a/src/tel/eden/mod/gui/PartyCreateScreen.java +++ b/src/tel/eden/mod/gui/PartyCreateScreen.java @@ -1,7 +1,10 @@ package tel.eden.mod.gui; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; +import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; @@ -13,18 +16,20 @@ import net.minecraft.resources.Identifier; import tel.eden.mod.EdenModClient; -/** Screen GUI for creating a raid/Annihilation/Other party with auto-detected party size. */ +/** Screen GUI for creating a raid/Annihilation/Festival/Other party with auto-detected party size. */ public final class PartyCreateScreen extends EdenReferenceScreen { private static final int BASE_PANEL_WIDTH = 350; - private static final int BASE_PANEL_HEIGHT = 310; + private static final int BASE_PANEL_HEIGHT = 330; private final Screen parent; private final EdenModClient mod; private final String initialTarget; private final Set selectedTargets = new LinkedHashSet<>(); + private final List addedMembers = new ArrayList<>(); private int maxPartySize = 4; - private int playersInParty = 1; + private int filledSlots = 0; + private boolean sendToAlly = false; private Button btnNotg; private Button btnNol; @@ -33,22 +38,28 @@ public final class PartyCreateScreen extends EdenReferenceScreen { private Button btnWtp; private Button btnAnnihilation; private Button btnOther; + private Button btnFestival; private Button btnMaxMinus; private Button btnMaxPlus; - private Button btnPlayersMinus; - private Button btnPlayersPlus; + private Button btnFilledMinus; + private Button btnFilledPlus; + private Button btnAddPlayer; + private Button btnClearAdded; + private Button btnSendToAlly; private Button btnCreate; + private EditBox addPlayerField; private EditBox noteField; - private Identifier iconNotg; - private Identifier iconNol; - private Identifier iconTcc; - private Identifier iconTna; - private Identifier iconWtp; - private Identifier iconAnnihilation; - private Identifier iconOther; + private static final Identifier ICON_NOTG = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/notg.png"); + private static final Identifier ICON_NOL = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/nol.png"); + private static final Identifier ICON_TCC = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/tcc.png"); + private static final Identifier ICON_TNA = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/tna.png"); + private static final Identifier ICON_WTP = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/wtp.png"); + private static final Identifier ICON_ANNIHILATION = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/annihilation.png"); + private static final Identifier ICON_OTHER = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/other.png"); + private static final Identifier ICON_FESTIVAL = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/festival.png"); private EdenPanelLayout layout; public PartyCreateScreen(Screen parent, EdenModClient mod) { @@ -60,7 +71,7 @@ public PartyCreateScreen(Screen parent, EdenModClient mod, String initialTarget) this.parent = parent; this.mod = mod; this.initialTarget = initialTarget; - this.playersInParty = Math.max(1, Math.min(maxPartySize - 1, getScoreboardPartySize())); + this.filledSlots = 0; } @Override @@ -83,6 +94,7 @@ protected void init() { // Row 4 btnOther = Button.builder(Component.literal(" Other"), b -> onTargetClick("Other")).bounds(layout.x(15), layout.y(108), layout.w(155), layout.h(24)).build(); + btnFestival = Button.builder(Component.literal(" Festival"), b -> onTargetClick("Festival Minigames")).bounds(layout.x(180), layout.y(108), layout.w(155), layout.h(24)).build(); this.addRenderableWidget(btnNotg); this.addRenderableWidget(btnNol); @@ -91,54 +103,89 @@ protected void init() { this.addRenderableWidget(btnWtp); this.addRenderableWidget(btnAnnihilation); this.addRenderableWidget(btnOther); + this.addRenderableWidget(btnFestival); // Max Party Size Adjusters - btnMaxMinus = Button.builder(Component.literal("-"), b -> adjustMaxPartySize(-1)).bounds(layout.x(220), layout.y(142), layout.w(20), layout.h(20)).build(); - btnMaxPlus = Button.builder(Component.literal("+"), b -> adjustMaxPartySize(1)).bounds(layout.x(270), layout.y(142), layout.w(20), layout.h(20)).build(); - + btnMaxMinus = Button.builder(Component.literal("-"), b -> adjustMaxPartySize(-1)).bounds(layout.x(220), layout.y(136), layout.w(20), layout.h(18)).build(); + btnMaxPlus = Button.builder(Component.literal("+"), b -> adjustMaxPartySize(1)).bounds(layout.x(270), layout.y(136), layout.w(20), layout.h(18)).build(); this.addRenderableWidget(btnMaxMinus); this.addRenderableWidget(btnMaxPlus); - // Players in Party Adjusters - btnPlayersMinus = Button.builder(Component.literal("-"), b -> adjustPlayersInParty(-1)).bounds(layout.x(220), layout.y(164), layout.w(20), layout.h(20)).build(); - btnPlayersPlus = Button.builder(Component.literal("+"), b -> adjustPlayersInParty(1)).bounds(layout.x(270), layout.y(164), layout.w(20), layout.h(20)).build(); + // Filled Slots Adjusters + btnFilledMinus = Button.builder(Component.literal("-"), b -> adjustFilledSlots(-1)).bounds(layout.x(220), layout.y(158), layout.w(20), layout.h(18)).build(); + btnFilledPlus = Button.builder(Component.literal("+"), b -> adjustFilledSlots(1)).bounds(layout.x(270), layout.y(158), layout.w(20), layout.h(18)).build(); + this.addRenderableWidget(btnFilledMinus); + this.addRenderableWidget(btnFilledPlus); + + // Add Player Field + Button + addPlayerField = new EditBox(this.font, layout.x(15), layout.y(182), layout.w(125), layout.h(18), Component.literal("Player IGN")); + addPlayerField.setMaxLength(16); + this.addRenderableWidget(addPlayerField); + + btnAddPlayer = Button.builder(Component.literal("Add"), b -> onAddPlayer()).bounds(layout.x(145), layout.y(182), layout.w(45), layout.h(18)).build(); + this.addRenderableWidget(btnAddPlayer); + + // Send to Ally toggle + btnSendToAlly = Button.builder(Component.literal(sendToAlly ? "§a[✔] Ally" : "§7[ ] Ally"), b -> toggleSendToAlly()).bounds(layout.x(200), layout.y(182), layout.w(135), layout.h(18)).build(); + this.addRenderableWidget(btnSendToAlly); - this.addRenderableWidget(btnPlayersMinus); - this.addRenderableWidget(btnPlayersPlus); + // Clear Added Members Button (only visible when addedMembers is non-empty) + btnClearAdded = Button.builder(Component.literal("§cClear"), b -> onClearAdded()).bounds(layout.x(280), layout.y(204), layout.w(55), layout.h(16)).build(); + btnClearAdded.visible = !addedMembers.isEmpty(); + this.addRenderableWidget(btnClearAdded); // Note Text Field - noteField = new EditBox(this.font, layout.x(15), layout.y(206), layout.w(320), layout.h(20), Component.literal("Party Note")); + noteField = new EditBox(this.font, layout.x(15), layout.y(236), layout.w(320), layout.h(18), Component.literal("Party Note")); noteField.setMaxLength(100); this.addRenderableWidget(noteField); // Action Buttons - btnCreate = Button.builder(Component.literal("Create Party"), b -> onCreate()).bounds(layout.x(15), layout.y(276), layout.w(155), layout.h(20)).build(); - Button btnCancel = Button.builder(Component.literal("Cancel"), b -> onClose()).bounds(layout.x(180), layout.y(276), layout.w(155), layout.h(20)).build(); - + btnCreate = Button.builder(Component.literal("Create Party"), b -> onCreate()).bounds(layout.x(15), layout.y(294), layout.w(155), layout.h(20)).build(); + Button btnCancel = Button.builder(Component.literal("Cancel"), b -> onClose()).bounds(layout.x(180), layout.y(294), layout.w(155), layout.h(20)).build(); this.addRenderableWidget(btnCreate); this.addRenderableWidget(btnCancel); - // Register custom icon textures - iconNotg = registerDynamicIcon("notg"); - iconNol = registerDynamicIcon("nol"); - iconTcc = registerDynamicIcon("tcc"); - iconTna = registerDynamicIcon("tna"); - iconWtp = registerDynamicIcon("wtp"); - iconAnnihilation = registerDynamicIcon("annihilation"); - iconOther = registerDynamicIcon("other"); applyInitialTarget(); + trimToMax(); updateWidgetStates(); } + private int occupancy() { + return 1 + addedMembers.size() + filledSlots; + } + + private void trimToMax() { + while (occupancy() > maxPartySize) { + if (filledSlots > 0) { + filledSlots--; + } else if (!addedMembers.isEmpty()) { + addedMembers.remove(addedMembers.size() - 1); + } else { + break; + } + } + } + + private void toggleSendToAlly() { + sendToAlly = !sendToAlly; + btnSendToAlly.setMessage(Component.literal(sendToAlly ? "§a[✔] Ally" : "§7[ ] Ally")); + } + private void applyInitialTarget() { if (initialTarget == null || initialTarget.isBlank()) { return; } + if (initialTarget.equals("Festival Minigames") && (mod == null || !mod.isFestivalEnabled())) { + return; + } selectedTargets.clear(); if (initialTarget.equals("Annihilation")) { selectedTargets.add("Annihilation"); maxPartySize = 10; + } else if (initialTarget.equals("Festival Minigames")) { + selectedTargets.add("Festival Minigames"); + maxPartySize = 5; } else if (initialTarget.equals("Other")) { selectedTargets.add("Other"); maxPartySize = 4; @@ -146,30 +193,21 @@ private void applyInitialTarget() { selectedTargets.add(initialTarget); maxPartySize = 4; } - playersInParty = Math.max(1, Math.min(maxPartySize - 1, playersInParty)); } private void onTargetClick(String target) { - if (target.equals("Annihilation")) { - if (selectedTargets.contains("Annihilation")) { - selectedTargets.clear(); - maxPartySize = 4; - } else { - selectedTargets.clear(); - selectedTargets.add("Annihilation"); + if (target.equals("Annihilation") || target.equals("Festival Minigames") || target.equals("Other")) { + selectedTargets.clear(); + selectedTargets.add(target); + if (target.equals("Annihilation")) { maxPartySize = 10; - } - } else if (target.equals("Other")) { - if (selectedTargets.contains("Other")) { - selectedTargets.clear(); - maxPartySize = 4; + } else if (target.equals("Festival Minigames")) { + maxPartySize = 5; } else { - selectedTargets.clear(); - selectedTargets.add("Other"); maxPartySize = 4; } } else { - if (selectedTargets.contains("Annihilation") || selectedTargets.contains("Other")) { + if (selectedTargets.contains("Annihilation") || selectedTargets.contains("Festival Minigames") || selectedTargets.contains("Other")) { selectedTargets.clear(); } if (selectedTargets.contains(target)) { @@ -180,28 +218,58 @@ private void onTargetClick(String target) { maxPartySize = 4; } - if (playersInParty >= maxPartySize) { - playersInParty = maxPartySize - 1; + trimToMax(); + updateWidgetStates(); + } + + private void adjustMaxPartySize(int amount) { + if (selectedTargets.contains("Other") || selectedTargets.contains("Annihilation") || selectedTargets.contains("Festival Minigames")) { + maxPartySize = Math.max(Math.max(2, occupancy()), Math.min(10, maxPartySize + amount)); + updateWidgetStates(); } + } + private void adjustFilledSlots(int amount) { + int maxAllowed = Math.max(0, maxPartySize - 1 - addedMembers.size()); + filledSlots = Math.max(0, Math.min(maxAllowed, filledSlots + amount)); updateWidgetStates(); } - private void adjustMaxPartySize(int amount) { - if (selectedTargets.contains("Other") || selectedTargets.contains("Annihilation")) { - maxPartySize = Math.max(2, Math.min(10, maxPartySize + amount)); - if (playersInParty >= maxPartySize) { - playersInParty = maxPartySize - 1; + private void onAddPlayer() { + if (addPlayerField == null) + return; + String ign = addPlayerField.getValue().trim(); + if (ign.matches("[A-Za-z0-9_]{1,16}") && (mod == null || !ign.equalsIgnoreCase(mod.playerName())) && addedMembers.stream().noneMatch(m -> m.equalsIgnoreCase(ign))) { + if (occupancy() < maxPartySize) { + addedMembers.add(ign); + addPlayerField.setValue(""); + updateWidgetStates(); } - updateWidgetStates(); } } - private void adjustPlayersInParty(int amount) { - playersInParty = Math.max(1, Math.min(maxPartySize - 1, playersInParty + amount)); + private void onClearAdded() { + addedMembers.clear(); updateWidgetStates(); } + private String getCreateDisabledReason() { + if (selectedTargets.isEmpty()) { + return "Please select at least one target!"; + } + if (selectedTargets.contains("Annihilation") && (mod != null && !mod.isAnnihilationActive())) { + return "Annihilation parties can only be opened while the Annihilation timer is active. Check /party annihilation when."; + } + if (selectedTargets.contains("Festival Minigames") && (mod != null && !mod.isFestivalEnabled())) { + return "Festival Minigames party finder is currently disabled by Senate."; + } + int limit = Math.max(0, maxPartySize - 2 - addedMembers.size()); + if (filledSlots > limit) { + return "Filled slots must be between 0 and " + limit + "."; + } + return null; + } + private void updateWidgetStates() { btnNotg.setMessage(Component.literal(getButtonText("Nest of the Grootslangs", "NOTG"))); btnNol.setMessage(Component.literal(getButtonText("Orphion's Nexus of Light", "NOL"))); @@ -210,15 +278,24 @@ private void updateWidgetStates() { btnWtp.setMessage(Component.literal(getButtonText("The Wartorn Palace", "WTP"))); btnAnnihilation.setMessage(Component.literal(getButtonText("Annihilation", "Annihilation"))); btnOther.setMessage(Component.literal(getButtonText("Other", "Other"))); + btnFestival.setMessage(Component.literal(getButtonText("Festival Minigames", "Festival"))); + btnFestival.active = mod != null && mod.isFestivalEnabled(); - boolean canEditMax = selectedTargets.contains("Other") || selectedTargets.contains("Annihilation"); - btnMaxMinus.active = canEditMax && maxPartySize > 2; + boolean canEditMax = selectedTargets.contains("Other") || selectedTargets.contains("Annihilation") || selectedTargets.contains("Festival Minigames"); + btnMaxMinus.active = canEditMax && maxPartySize > Math.max(2, occupancy()); btnMaxPlus.active = canEditMax && maxPartySize < 10; - btnPlayersMinus.active = playersInParty > 1; - btnPlayersPlus.active = playersInParty < (maxPartySize - 1); + btnFilledMinus.active = filledSlots > 0; + btnFilledPlus.active = occupancy() < maxPartySize; + + if (btnAddPlayer != null) { + btnAddPlayer.active = occupancy() < maxPartySize; + } + if (btnClearAdded != null) { + btnClearAdded.visible = !addedMembers.isEmpty(); + } - btnCreate.active = !selectedTargets.isEmpty(); + btnCreate.active = (getCreateDisabledReason() == null); } private String getButtonText(String target, String displayName) { @@ -240,6 +317,8 @@ private String getShortTargetName(String target) { return "TNA"; if (target.equals("The Wartorn Palace")) return "WTP"; + if (target.equals("Festival Minigames")) + return "Festival"; return target; } @@ -255,45 +334,74 @@ public void render(GuiGraphics g, int mouseX, int mouseY, float delta) { super.render(g, scaledMouseX, scaledMouseY, delta); // Draw custom icons on buttons - drawButtonIcon(g, btnNotg, iconNotg); - drawButtonIcon(g, btnNol, iconNol); - drawButtonIcon(g, btnTcc, iconTcc); - drawButtonIcon(g, btnTna, iconTna); - drawButtonIcon(g, btnWtp, iconWtp); - drawButtonIcon(g, btnAnnihilation, iconAnnihilation); - drawButtonIcon(g, btnOther, iconOther); + drawButtonIcon(g, btnNotg, ICON_NOTG); + drawButtonIcon(g, btnNol, ICON_NOL); + drawButtonIcon(g, btnTcc, ICON_TCC); + drawButtonIcon(g, btnTna, ICON_TNA); + drawButtonIcon(g, btnWtp, ICON_WTP); + drawButtonIcon(g, btnAnnihilation, ICON_ANNIHILATION); + drawButtonIcon(g, btnOther, ICON_OTHER); + drawButtonIcon(g, btnFestival, ICON_FESTIVAL); // Draw Screen Title (Opaque White) g.drawCenteredString(this.font, this.title, layout.centerX(), layout.y(12), 0xFFFFFFFF); // Draw Labels & Values (Opaque Colors) - g.drawString(this.font, "Max Party Size:", layout.x(15), layout.y(148), 0xFFA0A0A0); - g.drawCenteredString(this.font, String.valueOf(this.maxPartySize), layout.x(255), layout.y(148), 0xFFFFFFFF); + g.drawString(this.font, "Max Party Size:", layout.x(15), layout.y(141), 0xFFA0A0A0); + g.drawCenteredString(this.font, String.valueOf(this.maxPartySize), layout.x(255), layout.y(141), 0xFFFFFFFF); - g.drawString(this.font, "Players in Party:", layout.x(15), layout.y(170), 0xFFA0A0A0); - g.drawCenteredString(this.font, String.valueOf(this.playersInParty), layout.x(255), layout.y(170), 0xFFFFFFFF); + g.drawString(this.font, "Filled Slots:", layout.x(15), layout.y(163), 0xFFA0A0A0); + g.drawCenteredString(this.font, String.valueOf(this.filledSlots), layout.x(255), layout.y(163), 0xFFFFFFFF); - g.drawString(this.font, "Party Note:", layout.x(15), layout.y(194), 0xFFA0A0A0); + // Draw Added Members if present + if (!addedMembers.isEmpty()) { + String membersText = "§aMembers: §f" + String.join(", ", addedMembers); + if (this.font.width(membersText) > layout.w(255)) { + membersText = "§aMembers: §f" + addedMembers.size() + " added"; + } + g.drawString(this.font, membersText, layout.x(15), layout.y(208), 0xFFFFFFFF); + } + + g.drawString(this.font, "Party Note:", layout.x(15), layout.y(224), 0xFFA0A0A0); // Draw Preview Message (Opaque Colors) if (selectedTargets.isEmpty()) { - g.drawCenteredString(this.font, "Please select at least one target!", layout.centerX(), layout.y(240), 0xFFFF5555); + g.drawCenteredString(this.font, "Please select at least one target!", layout.centerX(), layout.y(266), 0xFFFF5555); } else { String preview; + String scopeTag = sendToAlly ? "[Ally] " : "[Guild] "; if (selectedTargets.contains("Annihilation")) { - preview = "Annihilation (" + playersInParty + "/" + maxPartySize + ")"; + preview = scopeTag + "Annihilation (" + occupancy() + "/" + maxPartySize + ")"; + } else if (selectedTargets.contains("Festival Minigames")) { + preview = scopeTag + "Festival (" + occupancy() + "/" + maxPartySize + ")"; } else if (selectedTargets.contains("Other")) { - preview = "Other (" + playersInParty + "/" + maxPartySize + ")"; + preview = scopeTag + "Other (" + occupancy() + "/" + maxPartySize + ")"; } else { java.util.List shortNames = new java.util.ArrayList<>(); for (String target : selectedTargets) { shortNames.add(getShortTargetName(target)); } - preview = String.join(" / ", shortNames) + " (" + playersInParty + "/4)"; + preview = scopeTag + String.join(" / ", shortNames) + " (" + occupancy() + "/4)"; } - g.drawCenteredString(this.font, "Ready: " + preview, layout.centerX(), layout.y(240), 0xFF55FF55); + boolean ready = getCreateDisabledReason() == null; + g.drawCenteredString(this.font, (ready ? "Ready: " : "Preview: ") + preview, layout.centerX(), layout.y(266), ready ? 0xFF55FF55 : 0xFFA0A0A0); } popReferencePose(g); + + if (!btnCreate.active && isHovered(scaledMouseX, scaledMouseY, btnCreate)) { + String reason = getCreateDisabledReason(); + if (reason != null) { + g.setComponentTooltipForNextFrame(this.font, List.of(Component.literal(reason).withStyle(ChatFormatting.RED)), mouseX, mouseY); + } + } else if (mod != null && !mod.isAnnihilationActive() && isHovered(scaledMouseX, scaledMouseY, btnAnnihilation)) { + g.setComponentTooltipForNextFrame(this.font, List.of(Component.literal("Annihilation parties can only be opened while the Annihilation timer is active. Check /party annihilation when.").withStyle(ChatFormatting.RED)), mouseX, mouseY); + } else if (mod != null && !mod.isFestivalEnabled() && isHovered(scaledMouseX, scaledMouseY, btnFestival)) { + g.setComponentTooltipForNextFrame(this.font, List.of(Component.literal("Festival Minigames party finder is currently disabled by Senate.").withStyle(ChatFormatting.RED)), mouseX, mouseY); + } + } + + private static boolean isHovered(int mouseX, int mouseY, Button btn) { + return btn != null && mouseX >= btn.getX() && mouseX <= btn.getX() + btn.getWidth() && mouseY >= btn.getY() && mouseY <= btn.getY() + btn.getHeight(); } private void drawButtonIcon(GuiGraphics g, Button btn, Identifier icon) { @@ -315,36 +423,22 @@ private void onCreate() { String targetName; if (selectedTargets.contains("Annihilation")) { targetName = "Annihilation"; + } else if (selectedTargets.contains("Festival Minigames")) { + targetName = "Festival Minigames"; } else if (selectedTargets.contains("Other")) { targetName = "Other"; } else { targetName = String.join(" / ", selectedTargets); } - int filledSlots = playersInParty - 1; var ws = mod.socket(); if (ws != null) { - ws.sendPartyOpen(targetName, maxPartySize, noteField.getValue(), filledSlots); + ws.sendPartyOpen(targetName, maxPartySize, noteField.getValue(), filledSlots, sendToAlly, mod.currentWynnWorld(), addedMembers); } onClose(); } - private int getScoreboardPartySize() { - try { - var player = Minecraft.getInstance().player; - if (player != null) { - var scoreboard = player.level().getScoreboard(); - var team = scoreboard.getPlayersTeam(player.getScoreboardName()); - if (team != null && team.getName().startsWith("p_")) { - return Math.max(1, team.getPlayers().size()); - } - } - } catch (Exception ignored) { - } - return 1; - } - @Override public void onClose() { this.minecraft.setScreen(this.parent); @@ -369,21 +463,4 @@ public boolean mouseDragged(MouseButtonEvent event, double d, double e) { public boolean mouseScrolled(double mouseX, double mouseY, double d, double e) { return super.mouseScrolled(mouseX / uiScale, mouseY / uiScale, d, e); } - private Identifier registerDynamicIcon(String name) { - Identifier loc = Identifier.parse("edenmod:dynamic_icon/" + name); - var tm = Minecraft.getInstance().getTextureManager(); - try { - var stream = PartyCreateScreen.class.getClassLoader().getResourceAsStream("assets/edenmod/textures/gui/icons/" + name + ".png"); - if (stream != null) { - var img = com.mojang.blaze3d.platform.NativeImage.read(stream); - var texture = new net.minecraft.client.renderer.texture.DynamicTexture(() -> name, img); - tm.register(loc, texture); - stream.close(); - return loc; - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } } diff --git a/src/tel/eden/mod/gui/PartyListScreen.java b/src/tel/eden/mod/gui/PartyListScreen.java index 512ce5b..998aabc 100644 --- a/src/tel/eden/mod/gui/PartyListScreen.java +++ b/src/tel/eden/mod/gui/PartyListScreen.java @@ -13,8 +13,8 @@ public final class PartyListScreen extends EdenReferenceScreen { private static final int BASE_PANEL_WIDTH = 420; private static final int BASE_PANEL_HEIGHT = 300; - private static final int ROW_HEIGHT = 28; - private static final int VISIBLE_ROWS = 8; + private static final int ROW_HEIGHT = 44; + private static final int VISIBLE_ROWS = 5; private final Screen parent; private final EdenModClient mod; @@ -88,6 +88,8 @@ public void render(GuiGraphics g, int mouseX, int mouseY, float delta) { int listHeight = layout.h(ROW_HEIGHT * VISIBLE_ROWS); g.fill(listLeft, listTop, listLeft + listWidth, listTop + listHeight, 0x22000000); + PartyInfo hoveredParty = null; + if (lastKnownParties.isEmpty()) { g.drawCenteredString(this.font, "No active parties", layout.centerX(), layout.y(122), 0xFFAAAAAA); } else { @@ -97,32 +99,141 @@ public void render(GuiGraphics g, int mouseX, int mouseY, float delta) { break; } - int rowTop = layout.y(38 + visible * ROW_HEIGHT); - int rowBottom = rowTop + layout.h(24); - int joinLeft = layout.x(333); - int joinRight = layout.x(389); + PartyInfo party = lastKnownParties.get(index); + int cardTop = layout.y(38 + visible * ROW_HEIGHT); + int cardBottom = cardTop + layout.h(40); + int cardLeft = layout.x(17); + int cardRight = layout.x(391); + + boolean cardHovered = scaledMouseX >= cardLeft && scaledMouseX <= cardRight && scaledMouseY >= cardTop && scaledMouseY <= cardBottom; + if (cardHovered) { + hoveredParty = party; + } - g.fill(layout.x(17), rowTop, layout.x(391), rowBottom, 0x44282828); - drawJoinButton(g, joinLeft, rowTop, joinRight - joinLeft, layout.h(24), isJoinHovered(scaledMouseX, scaledMouseY, index)); + // Card background and subtle borders + g.fill(cardLeft, cardTop, cardRight, cardBottom, cardHovered ? 0x55353535 : 0x44252525); + g.fill(cardLeft, cardTop, cardRight, cardTop + 1, cardHovered ? 0x44FFFFFF : 0x22FFFFFF); + g.fill(cardLeft, cardBottom - 1, cardRight, cardBottom, 0x22000000); + + // Right side: Join / Full / In Party button + int btnLeft = layout.x(335); + int btnRight = layout.x(387); + int btnWidth = btnRight - btnLeft; + int btnTop = cardTop + layout.h(9); + int btnHeight = layout.h(22); + + boolean isSelf = mod != null && party.members().stream().anyMatch(m -> m.equalsIgnoreCase(mod.playerName())); + boolean isFull = isFull(party); + boolean joinHovered = cardHovered && scaledMouseX >= btnLeft && scaledMouseX <= btnRight && scaledMouseY >= btnTop && scaledMouseY <= btnTop + btnHeight; + + String btnLabel; + int btnTextColor; + boolean btnActive; + if (isSelf) { + btnLabel = "In Party"; + btnTextColor = 0xFFAAAAAA; + btnActive = false; + } else if (isFull) { + btnLabel = "Full"; + btnTextColor = 0xFFFF7777; + btnActive = false; + } else { + btnLabel = "Join"; + btnTextColor = 0xFFFFFFFF; + btnActive = true; + } + drawJoinButton(g, btnLeft, btnTop, btnWidth, btnHeight, btnLabel, btnTextColor, btnActive, joinHovered && btnActive); + + // Occupancy count badge before the button + int badgeRight = layout.x(330); + String countStr = party.size() + "/" + party.max(); + int countWidth = this.font.width(countStr); + g.drawString(this.font, countStr, badgeRight - countWidth, cardTop + layout.h(16), isFull ? 0xFFFF6666 : 0xFF55FF55); + + // Text lines on left + int textLeft = layout.x(22); + int textWidth = badgeRight - countWidth - textLeft - layout.w(8); + + // Line 1: Scope, ID, World, Raid name + String scope = party.ally() ? "§b[Ally] " : "§6[Guild] "; + String worldStr = party.world() != null && !party.world().isBlank() ? "§a[" + party.world() + "] " : ""; + String line1 = scope + "§f#" + party.id() + " " + worldStr + "§f§l" + party.raid(); + g.drawString(this.font, trimToWidth(line1, textWidth), textLeft, cardTop + layout.h(5), 0xFFFFFFFF); + + // Line 2: Host & Members + String hostStr = "§eHost: §f" + party.displayMember(0); + String line2; + if (party.size() > 1) { + List others = new ArrayList<>(); + for (int i = 1; i < party.size(); i++) { + others.add(party.displayMember(i)); + } + line2 = hostStr + " §7| §bMembers: §f" + String.join(", ", others); + } else { + line2 = hostStr + " §7| §8(Waiting for members)"; + } + g.drawString(this.font, trimToWidth(line2, textWidth), textLeft, cardTop + layout.h(17), 0xFFFFFFFF); - PartyInfo party = lastKnownParties.get(index); - String text = "#" + party.id() + " | " + party.raid() + " | Host: " + party.host() + " | [" + party.members().size() + "/" + party.max() + "]"; - g.drawString(this.font, trimToWidth(text, layout.w(306)), layout.x(24), rowTop + layout.h(8), 0xFFFFFFFF); - g.drawCenteredString(this.font, "Join", joinLeft + (layout.w(56) / 2), rowTop + layout.h(8), 0xFFFFFFFF); + // Line 3: Note (if present) + if (party.note() != null && !party.note().isBlank()) { + String line3 = "§8Note: §7" + party.note(); + g.drawString(this.font, trimToWidth(line3, textWidth), textLeft, cardTop + layout.h(28), 0xFFFFFFFF); + } } } layout.drawScrollbar(g, layout.x(393), listTop, layout.w(8), listHeight, VISIBLE_ROWS, lastKnownParties.size(), scrollOffset); popReferencePose(g); + + if (hoveredParty != null) { + g.setComponentTooltipForNextFrame(this.font, buildPartyTooltip(hoveredParty), mouseX, mouseY); + } + } + + private List buildPartyTooltip(PartyInfo party) { + List lines = new ArrayList<>(); + String scope = party.ally() ? "§b[Ally Party]" : "§6[Guild Party]"; + lines.add(Component.literal(scope + " §f#" + party.id() + " - §a§l" + party.raid())); + if (party.world() != null && !party.world().isBlank()) { + lines.add(Component.literal("§7World: §e" + party.world())); + } + lines.add(Component.literal("§eHost: §f" + party.displayMember(0))); + if (party.size() > 1) { + lines.add(Component.literal("§bMembers (" + (party.size() - 1) + "):")); + for (int i = 1; i < party.size(); i++) { + lines.add(Component.literal(" §7- §f" + party.displayMember(i))); + } + } + int openSlots = party.max() - party.size(); + if (openSlots > 0) { + lines.add(Component.literal("§8 + " + openSlots + " open slot" + (openSlots == 1 ? "" : "s"))); + } + if (party.note() != null && !party.note().isBlank()) { + lines.add(Component.literal("§8Note: §f" + party.note())); + } + boolean isSelf = mod != null && party.members().stream().anyMatch(m -> m.equalsIgnoreCase(mod.playerName())); + if (isSelf) { + lines.add(Component.literal("§7(You are already in this party)")); + } else if (isFull(party)) { + lines.add(Component.literal("§c(Party is full)")); + } else { + lines.add(Component.literal("§aClick \"Join\" to join this party")); + } + return lines; } - private void drawJoinButton(GuiGraphics g, int x, int y, int width, int height, boolean hovered) { - int fill = hovered ? 0xFF777777 : 0xFF5A5A5A; + private void drawJoinButton(GuiGraphics g, int x, int y, int width, int height, String label, int textColor, boolean active, boolean hovered) { + int fill = !active ? 0xFF353535 : (hovered ? 0xFF2E7D32 : 0xFF1E5224); + int borderTop = !active ? 0xFF4A4A4A : (hovered ? 0xFF4CAF50 : 0xFF2E7D32); + int borderBottom = !active ? 0xFF222222 : (hovered ? 0xFF1B5E20 : 0xFF143B17); + g.fill(x, y, x + width, y + height, fill); - g.fill(x, y, x + width, y + 1, 0xFFD8D8D8); - g.fill(x, y, x + 1, y + height, 0xFFD8D8D8); - g.fill(x + width - 1, y, x + width, y + height, 0xFF222222); - g.fill(x, y + height - 1, x + width, y + height, 0xFF222222); + g.fill(x, y, x + width, y + 1, borderTop); + g.fill(x, y, x + 1, y + height, borderTop); + g.fill(x + width - 1, y, x + width, y + height, borderBottom); + g.fill(x, y + height - 1, x + width, y + height, borderBottom); + + g.drawCenteredString(this.font, label, x + (width / 2), y + ((height - 8) / 2), textColor); } @Override @@ -203,7 +314,7 @@ private int rowAt(double mouseX, double mouseY) { } int row = ((int) mouseY - layout.y(36)) / layout.h(ROW_HEIGHT); int index = scrollOffset + row; - return index >= 0 && index < lastKnownParties.size() ? index : -1; + return index >= 0 && index < lastKnownParties.size() && row < VISIBLE_ROWS ? index : -1; } private boolean isJoinHovered(double mouseX, double mouseY, int index) { @@ -211,8 +322,14 @@ private boolean isJoinHovered(double mouseX, double mouseY, int index) { if (visibleRow < 0 || visibleRow >= VISIBLE_ROWS) { return false; } - int rowTop = layout.y(38 + visibleRow * ROW_HEIGHT); - return mouseX >= layout.x(333) && mouseX <= layout.x(389) && mouseY >= rowTop && mouseY <= rowTop + layout.h(24); + PartyInfo party = lastKnownParties.get(index); + boolean isSelf = mod != null && party.members().stream().anyMatch(m -> m.equalsIgnoreCase(mod.playerName())); + if (isSelf || isFull(party)) { + return false; + } + int cardTop = layout.y(38 + visibleRow * ROW_HEIGHT); + int btnTop = cardTop + layout.h(9); + return mouseX >= layout.x(335) && mouseX <= layout.x(387) && mouseY >= btnTop && mouseY <= btnTop + layout.h(22); } private void updateScrollFromMouse(double mouseY) { diff --git a/src/tel/eden/mod/gui/PartyManageScreen.java b/src/tel/eden/mod/gui/PartyManageScreen.java index c06b3cc..1b2a516 100644 --- a/src/tel/eden/mod/gui/PartyManageScreen.java +++ b/src/tel/eden/mod/gui/PartyManageScreen.java @@ -25,20 +25,23 @@ public final class PartyManageScreen extends EdenReferenceScreen { private final Screen parent; private final EdenModClient mod; - private final PartyInfo party; + private PartyInfo party; private int filledSlots; private int maxSize; private EditBox noteField; + private EditBox addPlayerField; private Button btnPlayersMinus; private Button btnPlayersPlus; private Button btnMaxMinus; private Button btnMaxPlus; + private Button btnOpenToAllies; + private Button btnAddPlayer; private Identifier iconTarget; - private final Map headCache = new HashMap<>(); - private final Set headFetching = new HashSet<>(); + private static final Map HEAD_CACHE = new java.util.concurrent.ConcurrentHashMap<>(); + private static final Set HEAD_FETCHING = java.util.concurrent.ConcurrentHashMap.newKeySet(); private EdenPanelLayout layout; public PartyManageScreen(Screen parent, EdenModClient mod, PartyInfo party) { @@ -57,71 +60,193 @@ public PartyManageScreen(Screen parent, EdenModClient mod, PartyInfo party) { this.maxSize = party.max(); } + @Override + public void tick() { + super.tick(); + if (mod != null) { + boolean found = false; + for (PartyInfo p : mod.knownParties()) { + if (p.id() == party.id()) { + found = true; + if (!p.equals(party)) { + this.party = p; + this.maxSize = p.max(); + int fSlots = 0; + for (String m : p.members()) { + if (m.equals("*filled*")) + fSlots++; + } + this.filledSlots = fSlots; + + String curNote = noteField != null ? noteField.getValue() : null; + boolean noteFocused = noteField != null && noteField.isFocused(); + String curAdd = addPlayerField != null ? addPlayerField.getValue() : null; + boolean addFocused = addPlayerField != null && addPlayerField.isFocused(); + + rebuildWidgets(); + + if (noteField != null) { + if (noteFocused && curNote != null) { + noteField.setValue(curNote); + noteField.setFocused(true); + } else { + noteField.setValue(p.note()); + } + } + if (addPlayerField != null && curAdd != null) { + addPlayerField.setValue(curAdd); + if (addFocused) { + addPlayerField.setFocused(true); + } + } + } + return; + } + } + if (!found && mod.socket() != null && mod.socket().isConnected()) { + onClose(); + } + } + } + @Override protected void init() { updateReferenceSpace(); layout = EdenPanelLayout.centered(virtualWidth, virtualHeight, BASE_PANEL_WIDTH, BASE_PANEL_HEIGHT); int rightColX = layout.x(190); - btnMaxMinus = Button.builder(Component.literal("-"), b -> adjustMax(-1)).bounds(layout.x(245), layout.y(100), layout.w(20), layout.h(20)).build(); - btnMaxPlus = Button.builder(Component.literal("+"), b -> adjustMax(1)).bounds(layout.x(295), layout.y(100), layout.w(20), layout.h(20)).build(); + String self = mod != null ? mod.playerName() : null; + boolean isHost = self != null && party.host().equalsIgnoreCase(self); + + // Max size adjusters + btnMaxMinus = Button.builder(Component.literal("-"), b -> adjustMax(-1)).bounds(layout.x(245), layout.y(106), layout.w(20), layout.h(18)).build(); + btnMaxPlus = Button.builder(Component.literal("+"), b -> adjustMax(1)).bounds(layout.x(295), layout.y(106), layout.w(20), layout.h(18)).build(); + btnMaxMinus.active = isHost && party.isCustomSize(); + btnMaxPlus.active = isHost && party.isCustomSize(); this.addRenderableWidget(btnMaxMinus); this.addRenderableWidget(btnMaxPlus); - btnPlayersMinus = Button.builder(Component.literal("-"), b -> adjustFilledSlots(-1)).bounds(layout.x(245), layout.y(124), layout.w(20), layout.h(20)).build(); - btnPlayersPlus = Button.builder(Component.literal("+"), b -> adjustFilledSlots(1)).bounds(layout.x(295), layout.y(124), layout.w(20), layout.h(20)).build(); + // Filled slots adjusters + btnPlayersMinus = Button.builder(Component.literal("-"), b -> adjustFilledSlots(-1)).bounds(layout.x(245), layout.y(128), layout.w(20), layout.h(18)).build(); + btnPlayersPlus = Button.builder(Component.literal("+"), b -> adjustFilledSlots(1)).bounds(layout.x(295), layout.y(128), layout.w(20), layout.h(18)).build(); + btnPlayersMinus.active = isHost; + btnPlayersPlus.active = isHost; this.addRenderableWidget(btnPlayersMinus); this.addRenderableWidget(btnPlayersPlus); - noteField = new EditBox(this.font, rightColX, layout.y(175), layout.w(160), layout.h(20), Component.literal("Party Note")); + // Open to Allies toggle button + btnOpenToAllies = Button.builder(Component.literal(party.ally() ? "§a[✔] Open to Allies" : "§7[ ] Open to Allies"), b -> onToggleAlly()).bounds(rightColX, layout.y(152), layout.w(160), layout.h(18)).build(); + btnOpenToAllies.active = isHost; + this.addRenderableWidget(btnOpenToAllies); + + // Note field + noteField = new EditBox(this.font, rightColX, layout.y(186), layout.w(160), layout.h(18), Component.literal("Party Note")); noteField.setMaxLength(100); noteField.setValue(party.note()); + noteField.setEditable(isHost); this.addRenderableWidget(noteField); - Button btnCreateInGame = Button.builder(Component.literal("Create In-game"), b -> { - mod.createInGameParty(party.members()); - onClose(); - }).bounds(rightColX, layout.y(220), layout.w(160), layout.h(20)).build(); - Button btnUpdate = Button.builder(Component.literal("Update Party"), b -> onUpdate()).bounds(rightColX, layout.y(245), layout.w(160), layout.h(20)).build(); - Button btnDisband = Button.builder(Component.literal("§cDisband Party"), b -> onDisband()).bounds(rightColX, layout.y(270), layout.w(160), layout.h(20)).build(); - Button btnCancel = Button.builder(Component.literal("Cancel"), b -> onClose()).bounds(rightColX, layout.y(295), layout.w(160), layout.h(20)).build(); - - this.addRenderableWidget(btnCreateInGame); - this.addRenderableWidget(btnUpdate); - this.addRenderableWidget(btnDisband); - this.addRenderableWidget(btnCancel); + if (isHost) { + Button btnCreateInGame = Button.builder(Component.literal("Create In-game"), b -> { + mod.createInGameParty(party.members()); + onClose(); + }).bounds(rightColX, layout.y(214), layout.w(160), layout.h(18)).build(); + Button btnUpdate = Button.builder(Component.literal("Update Party"), b -> onUpdate()).bounds(rightColX, layout.y(236), layout.w(160), layout.h(18)).build(); + Button btnDisband = Button.builder(Component.literal("§cDisband Party"), b -> onDisband()).bounds(rightColX, layout.y(258), layout.w(160), layout.h(18)).build(); + Button btnCancel = Button.builder(Component.literal("Close"), b -> onClose()).bounds(rightColX, layout.y(280), layout.w(160), layout.h(18)).build(); + + this.addRenderableWidget(btnCreateInGame); + this.addRenderableWidget(btnUpdate); + this.addRenderableWidget(btnDisband); + this.addRenderableWidget(btnCancel); + } else { + Button btnLeave = Button.builder(Component.literal("§cLeave Party"), b -> onLeave()).bounds(rightColX, layout.y(258), layout.w(160), layout.h(18)).build(); + Button btnCancel = Button.builder(Component.literal("Close"), b -> onClose()).bounds(rightColX, layout.y(280), layout.w(160), layout.h(18)).build(); + + this.addRenderableWidget(btnLeave); + this.addRenderableWidget(btnCancel); + } + // Target icon String target = party.raid().toLowerCase(); if (target.contains("grootslangs")) - iconTarget = registerDynamicIcon("notg"); + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/notg.png"); else if (target.contains("nexus")) - iconTarget = registerDynamicIcon("nol"); + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/nol.png"); else if (target.contains("colossus")) - iconTarget = registerDynamicIcon("tcc"); + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/tcc.png"); else if (target.contains("anomaly")) - iconTarget = registerDynamicIcon("tna"); + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/tna.png"); else if (target.contains("palace")) - iconTarget = registerDynamicIcon("wtp"); + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/wtp.png"); else if (target.contains("annihilation")) - iconTarget = registerDynamicIcon("annihilation"); + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/annihilation.png"); + else if (target.contains("festival")) + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/festival.png"); else - iconTarget = registerDynamicIcon("other"); + iconTarget = Identifier.fromNamespaceAndPath("edenmod", "textures/gui/icons/other.png"); - int yPos = layout.y(40); + // Left column: Member rows (Kick button) + int yPos = layout.y(38); + int memberIdx = 0; for (String member : party.members()) { - if (member.equals("*filled*")) + if (member.equals("*filled*")) { + memberIdx++; continue; - if (!member.equalsIgnoreCase(party.host())) { - String finalMember = member; - Button btnKick = Button.builder(Component.literal("§c\u2716"), b -> onKick(finalMember)).bounds(layout.x(145), yPos - Math.max(1, layout.h(3)), layout.w(20), layout.h(20)).build(); + } + final String currentMember = member; + + // Kick button (host only, cannot kick self) + if (isHost && !member.equalsIgnoreCase(party.host())) { + Button btnKick = Button.builder(Component.literal("§c\u2716"), b -> onKick(currentMember)).bounds(layout.x(148), yPos - Math.max(1, layout.h(2)), layout.w(18), layout.h(16)).build(); this.addRenderableWidget(btnKick); } + yPos += layout.h(24); + memberIdx++; + } + + // Add Player by IGN (Host only, bottom of left column) + if (isHost) { + addPlayerField = new EditBox(this.font, layout.x(10), layout.y(280), layout.w(110), layout.h(18), Component.literal("IGN")); + addPlayerField.setMaxLength(16); + this.addRenderableWidget(addPlayerField); + + btnAddPlayer = Button.builder(Component.literal("Add"), b -> onAddPlayer()).bounds(layout.x(124), layout.y(280), layout.w(44), layout.h(18)).build(); + this.addRenderableWidget(btnAddPlayer); } updateWidgetStates(); } + private void onToggleAlly() { + var ws = mod != null ? mod.socket() : null; + if (ws != null) { + ws.sendPartyAllyToggle(!party.ally()); + } + } + + private void onAddPlayer() { + if (addPlayerField == null) + return; + String ign = addPlayerField.getValue().trim(); + if (ign.matches("[A-Za-z0-9_]{1,16}") && (realMembers() + filledSlots < maxSize) && party.members().stream().noneMatch(m -> m.equalsIgnoreCase(ign))) { + var ws = mod != null ? mod.socket() : null; + if (ws != null) { + ws.sendPartyManage("add", null, 0, ign); + addPlayerField.setValue(""); + } + } + } + + private void onLeave() { + var ws = mod != null ? mod.socket() : null; + if (ws != null) { + ws.sendPartyLeave(party.id()); + } + onClose(); + } + /** Members occupying a real slot (i.e. excluding the ``*filled*`` placeholders). */ private int realMembers() { int filled = 0; @@ -134,24 +259,48 @@ private int realMembers() { private void adjustFilledSlots(int amount) { int limit = maxSize - realMembers(); - filledSlots = Math.max(0, Math.min(limit, filledSlots + amount)); - updateWidgetStates(); + int next = Math.max(0, Math.min(limit, filledSlots + amount)); + if (next != filledSlots) { + filledSlots = next; + updateWidgetStates(); + var ws = mod != null ? mod.socket() : null; + if (ws != null) { + ws.sendPartyManage("filled", null, filledSlots, null); + } + } } private void adjustMax(int amount) { int floor = Math.max(2, realMembers() + filledSlots); - maxSize = Math.max(floor, Math.min(10, maxSize + amount)); - filledSlots = Math.min(filledSlots, Math.max(0, maxSize - realMembers())); - updateWidgetStates(); + int next = Math.max(floor, Math.min(10, maxSize + amount)); + if (next != maxSize) { + maxSize = next; + filledSlots = Math.min(filledSlots, Math.max(0, maxSize - realMembers())); + updateWidgetStates(); + var ws = mod != null ? mod.socket() : null; + if (ws != null) { + ws.sendPartyManage("max", null, maxSize, null); + } + } } private void updateWidgetStates() { + String self = mod != null ? mod.playerName() : null; + boolean isHost = self != null && party.host().equalsIgnoreCase(self); + int real = realMembers(); int occupancy = real + filledSlots; - btnPlayersMinus.active = filledSlots > 0; - btnPlayersPlus.active = occupancy < maxSize; - btnMaxMinus.active = maxSize > Math.max(2, occupancy); - btnMaxPlus.active = maxSize < 10; + btnPlayersMinus.active = isHost && filledSlots > 0; + btnPlayersPlus.active = isHost && occupancy < maxSize; + btnMaxMinus.active = isHost && party.isCustomSize() && maxSize > Math.max(2, occupancy); + btnMaxPlus.active = isHost && party.isCustomSize() && maxSize < 10; + if (btnOpenToAllies != null) { + btnOpenToAllies.active = isHost; + btnOpenToAllies.setMessage(Component.literal(party.ally() ? "§a[✔] Open to Allies" : "§7[ ] Open to Allies")); + } + if (btnAddPlayer != null) { + btnAddPlayer.active = isHost && occupancy < maxSize; + } } @Override @@ -167,19 +316,23 @@ public void render(GuiGraphics g, int mouseX, int mouseY, float delta) { super.render(g, scaledMouseX, scaledMouseY, delta); g.drawCenteredString(this.font, this.title, layout.centerX(), layout.y(12), 0xFFFFFFFF); - g.drawCenteredString(this.font, "Party Members (" + (realMembers() + filledSlots) + "/" + maxSize + ")", layout.x(87), layout.y(20), 0xFF55FF55); + String scopeTag = party.ally() ? "§b[Ally] " : "§6[Guild] "; + g.drawCenteredString(this.font, scopeTag + "Party (" + (realMembers() + filledSlots) + "/" + maxSize + ")", layout.x(87), layout.y(20), 0xFF55FF55); - int yPos = layout.y(40); + int yPos = layout.y(38); int headSize = Math.max(8, layout.h(12)); + int memberIdx = 0; for (String member : party.members()) { - if (member.equals("*filled*")) + if (member.equals("*filled*")) { + memberIdx++; continue; + } - Identifier headId = headCache.get(member); + Identifier headId = HEAD_CACHE.get(member); if (headId == null) { g.fill(layout.x(10), yPos - 1, layout.x(10) + headSize, yPos - 1 + headSize, 0xFF444444); - if (!headFetching.contains(member)) { - headFetching.add(member); + if (!HEAD_FETCHING.contains(member)) { + HEAD_FETCHING.add(member); java.util.concurrent.CompletableFuture.runAsync(() -> fetchHead(member)); } } else { @@ -190,62 +343,56 @@ public void render(GuiGraphics g, int mouseX, int mouseY, float delta) { g.pose().popMatrix(); } - g.drawString(this.font, member, layout.x(28), yPos + 1, member.equalsIgnoreCase(party.host()) ? 0xFFFFD700 : 0xFFFFFFFF); + String displayName = party.displayMember(memberIdx); + g.drawString(this.font, displayName, layout.x(26), yPos + 2, member.equalsIgnoreCase(party.host()) ? 0xFFFFD700 : 0xFFFFFFFF); yPos += layout.h(24); + memberIdx++; } int extraRows = Math.max(0, maxSize - realMembers()); for (int i = 0; i < extraRows; i++) { + if (yPos > layout.y(270)) + break; g.fill(layout.x(10), yPos - 1, layout.x(10) + headSize, yPos - 1 + headSize, 0xFF222222); - g.drawString(this.font, i < filledSlots ? "§7*Filled Slot*" : "§8*Empty*", layout.x(28), yPos + 1, 0xFFAAAAAA); + g.drawString(this.font, i < filledSlots ? "§7*Filled Slot*" : "§8*Empty*", layout.x(26), yPos + 2, 0xFFAAAAAA); yPos += layout.h(24); } if (iconTarget != null) { - int iconSize = Math.max(16, layout.h(28)); + int iconSize = Math.max(16, layout.h(24)); g.pose().pushMatrix(); - g.pose().translate(layout.x(256), layout.y(40)); + g.pose().translate(layout.x(258), layout.y(34)); g.pose().scale(iconSize / 64.0f, iconSize / 64.0f); g.blit(RenderPipelines.GUI_TEXTURED, iconTarget, 0, 0, 0.0f, 0.0f, 64, 64, 64, 64); g.pose().popMatrix(); } - g.drawCenteredString(this.font, party.raid(), layout.x(270), layout.y(74), 0xFF55FF55); + g.drawCenteredString(this.font, party.raid(), layout.x(270), layout.y(62), 0xFF55FF55); + if (party.world() != null && !party.world().isBlank()) { + g.drawCenteredString(this.font, "§eServer: " + party.world(), layout.x(270), layout.y(74), 0xFFFFFF55); + } - g.drawString(this.font, "Max Size:", layout.x(190), layout.y(106), 0xFFAAAAAA); - g.drawCenteredString(this.font, String.valueOf(this.maxSize), layout.x(280), layout.y(106), 0xFFFFFFFF); + g.drawString(this.font, "Max Size:", layout.x(190), layout.y(110), 0xFFAAAAAA); + g.drawCenteredString(this.font, String.valueOf(this.maxSize), layout.x(275), layout.y(110), 0xFFFFFFFF); - g.drawString(this.font, "Filled Slots:", layout.x(190), layout.y(130), 0xFFAAAAAA); - g.drawCenteredString(this.font, String.valueOf(this.filledSlots), layout.x(280), layout.y(130), 0xFFFFFFFF); + g.drawString(this.font, "Filled Slots:", layout.x(190), layout.y(132), 0xFFAAAAAA); + g.drawCenteredString(this.font, String.valueOf(this.filledSlots), layout.x(275), layout.y(132), 0xFFFFFFFF); - g.drawString(this.font, "Party Note:", layout.x(190), layout.y(162), 0xFFAAAAAA); + g.drawString(this.font, "Party Note:", layout.x(190), layout.y(174), 0xFFAAAAAA); popReferencePose(g); } private void onUpdate() { - var ws = mod.socket(); + var ws = mod != null ? mod.socket() : null; if (ws != null) { - if (this.maxSize != party.max()) { - ws.sendPartyManage("max", null, this.maxSize, null); - } - - if (!noteField.getValue().equals(party.note())) { + if (noteField != null && !noteField.getValue().equals(party.note())) { ws.sendPartyManage("note", noteField.getValue(), 0, null); } - - int fSlots = 0; - for (String member : party.members()) { - if (member.equals("*filled*")) - fSlots++; - } - if (this.filledSlots != fSlots) { - ws.sendPartyManage("filled", null, this.filledSlots, null); - } } onClose(); } private void onDisband() { - var ws = mod.socket(); + var ws = mod != null ? mod.socket() : null; if (ws != null) { ws.sendPartyManage("close", null, 0, null); } @@ -253,11 +400,10 @@ private void onDisband() { } private void onKick(String ign) { - var ws = mod.socket(); + var ws = mod != null ? mod.socket() : null; if (ws != null) { ws.sendPartyManage("remove", null, 0, ign); } - onClose(); } @Override @@ -284,23 +430,6 @@ public boolean mouseDragged(MouseButtonEvent event, double d, double e) { public boolean mouseScrolled(double mouseX, double mouseY, double d, double e) { return super.mouseScrolled(mouseX / uiScale, mouseY / uiScale, d, e); } - private Identifier registerDynamicIcon(String name) { - Identifier loc = Identifier.parse("edenmod:dynamic_icon/" + name); - var tm = Minecraft.getInstance().getTextureManager(); - try { - var stream = PartyManageScreen.class.getClassLoader().getResourceAsStream("assets/edenmod/textures/gui/icons/" + name + ".png"); - if (stream != null) { - var img = NativeImage.read(stream); - var texture = new DynamicTexture(() -> name, img); - tm.register(loc, texture); - stream.close(); - return loc; - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } private void fetchHead(String member) { if (!member.matches("[A-Za-z0-9_]{1,16}")) @@ -315,7 +444,7 @@ private void fetchHead(String member) { Identifier loc = Identifier.parse("edenmod:head/" + member.toLowerCase()); var texture = new DynamicTexture(() -> member, img); Minecraft.getInstance().getTextureManager().register(loc, texture); - headCache.put(member, loc); + HEAD_CACHE.put(member, loc); }); } } catch (Exception e) { diff --git a/src/tel/eden/mod/net/BridgeWebSocketClient.java b/src/tel/eden/mod/net/BridgeWebSocketClient.java index ba7408f..150f8f3 100644 --- a/src/tel/eden/mod/net/BridgeWebSocketClient.java +++ b/src/tel/eden/mod/net/BridgeWebSocketClient.java @@ -116,6 +116,10 @@ default void onRewardOptOutReply(String rewardKind, boolean blocked, String erro /** A short result line for a party action the player just took in-game. */ void onPartyFeedback(String message, String color); + /** Party configuration update (e.g. Festival Minigames toggle, Annihilation active). */ + default void onPartyConfig(boolean festivalEnabled, boolean annihilationActive) { + } + /** Feedback on game commands (e.g. cooldown). */ void onGameFeedback(String message, String color); @@ -549,8 +553,8 @@ private boolean sendTypeIfConnected(String type) { return true; } - /** Open a new party in-game for the given label (raid name or Annihilation). */ - public void sendPartyOpen(String raid, int maxSize, String note, int filled) { + /** Open a new party in-game for the given label (raid name, Annihilation, or Festival). */ + public void sendPartyOpen(String raid, int maxSize, String note, int filled, boolean ally, String world, java.util.List members) { WebSocket current = socket; if (current == null) { return; @@ -565,9 +569,30 @@ public void sendPartyOpen(String raid, int maxSize, String note, int filled) { if (filled > 0) { obj.addProperty("filled", filled); } + if (ally) { + obj.addProperty("ally", true); + } + if (world != null && !world.isBlank()) { + obj.addProperty("world", world); + } + if (members != null && !members.isEmpty()) { + com.google.gson.JsonArray arr = new com.google.gson.JsonArray(); + for (String m : members) { + arr.add(m); + } + obj.add("members", arr); + } enqueueSend(current, obj.toString()); } + public void sendPartyOpen(String raid, int maxSize, String note, int filled, boolean ally, String world) { + sendPartyOpen(raid, maxSize, note, filled, ally, world, java.util.List.of()); + } + + public void sendPartyOpen(String raid, int maxSize, String note, int filled) { + sendPartyOpen(raid, maxSize, note, filled, false, ""); + } + /** Join the open raid party with the given id. */ public void sendPartyJoin(int id) { WebSocket current = socket; @@ -580,6 +605,10 @@ public void sendPartyJoin(int id) { enqueueSend(current, obj.toString()); } + public void sendPartyAllyToggle(boolean ally) { + sendPartyManage("ally", ally ? "true" : "false", ally ? 1 : 0, ""); + } + /** Leave a raid party ({@code null} id = whichever party you are in). */ public void sendPartyLeave(Integer id) { WebSocket current = socket; @@ -1395,7 +1424,17 @@ private void handlePayload(String payload) { case "rewardOptOutReply" -> sink.onRewardOptOutReply(get(obj, "rewardKind"), getBool(obj, "blocked"), get(obj, "error"), get(obj, "color")); case "giftLockReply" -> sink.onGiftLockReply(getBool(obj, "granted"), get(obj, "error"), get(obj, "holder"), getInt(obj, "retryAfterSeconds", 0)); case "partyUpdate" -> sink.onPartyUpdate(get(obj, "event"), get(obj, "actor"), parseParty(obj), get(obj, "color")); - case "partyListReply" -> sink.onPartyList(parsePartyList(obj), get(obj, "color")); + case "partyListReply" -> { + if (sink != null && obj.has("festivalEnabled")) { + sink.onPartyConfig(getBool(obj, "festivalEnabled"), getBool(obj, "annihilationActive")); + } + sink.onPartyList(parsePartyList(obj), get(obj, "color")); + } + case "partyConfig" -> { + if (sink != null) { + sink.onPartyConfig(getBool(obj, "festivalEnabled"), getBool(obj, "annihilationActive")); + } + } case "partyFeedback" -> sink.onPartyFeedback(get(obj, "message"), get(obj, "color")); case "gameFeedback" -> sink.onGameFeedback(get(obj, "message"), get(obj, "color")); case "pillMessage" -> sink.onPillMessage(get(obj, "label"), get(obj, "content"), get(obj, "color"), getBool(obj, "bold")); @@ -1556,7 +1595,7 @@ private static float getFloat(JsonObject obj, String key, float fallback) { } private static PartyInfo parseParty(JsonObject obj) { - return new PartyInfo(getInt(obj, "id", 0), get(obj, "raid"), get(obj, "host"), getStringArray(obj, "members"), getInt(obj, "max", 4), get(obj, "note")); + return new PartyInfo(getInt(obj, "id", 0), get(obj, "raid"), get(obj, "host"), getStringArray(obj, "members"), getInt(obj, "max", 4), get(obj, "note"), getStringArray(obj, "tags"), getBool(obj, "ally"), get(obj, "world")); } private static java.util.List parsePartyList(JsonObject obj) { diff --git a/src/tel/eden/mod/net/PartyInfo.java b/src/tel/eden/mod/net/PartyInfo.java index 088e4d3..d787624 100644 --- a/src/tel/eden/mod/net/PartyInfo.java +++ b/src/tel/eden/mod/net/PartyInfo.java @@ -6,13 +6,43 @@ * A raid party snapshot received from the bridge backend (in a {@code partyUpdate} * event or a {@code partyListReply}). {@code members} is host-first. */ -public record PartyInfo(int id, String raid, String host, List members, int max, String note) { +public record PartyInfo(int id, String raid, String host, List members, int max, String note, List tags, boolean ally, String world) { public PartyInfo { members = List.copyOf(members); + tags = tags != null ? List.copyOf(tags) : List.of(); + world = world != null ? world : ""; + } + + public PartyInfo(int id, String raid, String host, List members, int max, String note) { + this(id, raid, host, members, max, note, List.of(), false, ""); } /** Current occupancy (e.g. {@code 2} of {@code max}). */ public int size() { return members.size(); } + + public String tagOf(int index) { + if (index >= 0 && index < tags.size()) { + String t = tags.get(index); + if (t != null) + return t; + } + return ""; + } + + public String displayMember(int index) { + if (index < 0 || index >= members.size()) + return ""; + String m = members.get(index); + String tag = tagOf(index); + if (!tag.isEmpty()) { + return "[" + tag + "] " + m; + } + return m; + } + + public boolean isCustomSize() { + return "Annihilation".equalsIgnoreCase(raid) || "Festival Minigames".equalsIgnoreCase(raid) || "Other".equalsIgnoreCase(raid); + } } From 43e607d97d9e38782960fb0f8fb4a17bb5144070 Mon Sep 17 00:00:00 2001 From: Good627 <193092356+Good627@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:10:24 +0300 Subject: [PATCH 2/5] fix(party): Assign correct raid icons to NOTG, TCC, WTP, NOL, and TNA --- .../assets/edenmod/textures/gui/icons/nol.png | Bin 9214 -> 6947 bytes .../edenmod/textures/gui/icons/notg.png | Bin 8852 -> 7582 bytes .../assets/edenmod/textures/gui/icons/tcc.png | Bin 8662 -> 5579 bytes .../assets/edenmod/textures/gui/icons/tna.png | Bin 8749 -> 5198 bytes .../assets/edenmod/textures/gui/icons/wtp.png | Bin 8410 -> 6403 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/assets/edenmod/textures/gui/icons/nol.png b/resources/assets/edenmod/textures/gui/icons/nol.png index 00db3530002df47871c425e76143c12d1ff3136f..6f1a6710aeab20ea8c0ac8d1297838785734f4f7 100644 GIT binary patch literal 6947 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 literal 9214 zcmV4Tx0C=2zkh@C*K^%m?^MMd!AzD~iTqPD!!$r`>!UqIJ@bCditS*ve zV|px}&|6;6(C}s1o)|UFX~o~~Z@u4Fv$uV!t6bUJ8s3av7LLwl)*iYB_+Nok+OvUA zAZ!b_2wDg*SBpaNI?(CH;t9~U6=$owuQ*xdV?Q%LI}X}4pxZes1zH&hC4czYA6&?< zsDG8z^})2WFsFKeZYEg5Ly*U!gpNt%kNlvYA7>ds36~|ZU*8l_oQVh`Na^F)N{Xc&GKXz~cgr$J6cI{e@BLsvUCwPEeWB0Lx zu3gJ5T?&ECk)P|oWRTB!F1AoY2vOMwoql8!MF52Wd=TLBnr$wxO*E(*O{Wk^>j^-O z0SE&K0g%QYWDH?rF{Q%hoMh;qmxPedVGc$#cO0S{0L=HhYzuuh+ib5W*10S~k;81V z*(@S#JO{6Xhgk$vsv{$7NXs&kvYL*hmC;aA?g}K*d;3Py+W~zCfOg|$#)=7hkC11C z{{M;r{91tCPKcf-nl0!HcG0x5+~r(e;->& zRG7^K4$%aUl_zeSz_~2~%p#kLA|a{h4`I z4l)g)biz*P|5XeijBbt)El>ComqomOR=M|_Ih9^>mDd5HakM{@(b|G>t~VSbfkcMH zrR-5{pCjz2))wTq}NbkM0~F4|n`)Jr@fuTT)i zjIHe<`R@mZU)FVf9mkOt_Lxp7|IgS6@+paDs;9HNDaCA?DBiZPwqVtw`a+bttca!+ zb>Bdg+cOa6dc!GX6~&m@Y%;@aHo+vA!1Dl41Ves|tunkqZV8N^1Ue`PG^&HDOhN!z zRgjfrsBAs}R;N zo?{55x-q?`X^>SF$y63L6OR>hDzR*8k!CRo{9A{H@#4<@ok>ag7Dq^H-dm`jg#iLb zpz0K>OFgznu2@p%Y$$T5o7=+t-}d#9(Rda%n-w;T8H8}gVkxNMvl-DGW5%&BBgo%N zKeEe5ujigU-u3bC^hw5(y)T=UkV?xaa$2x*ZaMwp)MBk`Bq==D(w-QMq%Y?PdErD% zAfK@FS2vcp*I%=&396=Of7v#`@9GP|Y7ybES`3QD7sT^|vEv^%*fm*UMot08bFi4j zj}R#cJTe&>St$$AY=S6?6LU|NqH#uy8_RK!l*~wmdd#i%;hU$`XjB*YdtN_?zDVL4 zj*#Df923Y1h`(W0rSI-*&uGvO1`>F9YbO_%GJ^PNiOEvE!NjecX}Z@QoZHzdarkxMPXubHraatHO8p^~)QyHx7=F zb#ENvRPZSBI1R;Q#IuD7f?ymcBAzWQkx?*jiUV#N2baqYr`>57OhN>h%_2f$DO~uq zW?cELQ=zFQ*lczX!cVA?z~05@&KXlAMK*lg(f6|rVNQym*z1BqIsAERC-=cXh+H7b+%q5c5wFV$247X?oASGEGMPR` z&zT-`#x=IMWLC%GCMTwrSkXSB!sm8P%)^)yi)FBIemU0N^%Xq#+%E7!4jVAx6a*7e zX$jLRoq3N!AeeZ}n_CA(Rt>evt%a#y&bFwU2CKykpWA^=?E$i>ZGgLEUbR|N=w7E& zx`x@c$xPtJH;Mp^_)}_%oDW=gW`p+p&H?_l{Q*)`;4mg1M{Z2q8DYmGoZZKWA2(*Q zqNC73ao+qQys>u#PP+}dc2w_~ao02jcink5o_Tf;_8$tv<;d9vw&Q_N0+j^>#V#|l zDn%xvV#YK-&OWmNsgyLK|Co>l=JjBB4Z?9KayzlLFG${O8{#jUU#pdS><{P^r!ZO> zDHxC5m~nMDQE*aq;S)EXHJ#slI7nVQG)(+H7pR`I$~oe5B6otA=;$XxkjltdI?aWF zSO%Rz1x}k8y3W*x!o&lKXbN9iR)<;5#d!S5-SD{0&~yzMSw}RH#)4W4&Ykbadu>s8 z-8RJHNnCm5LO2{IC@Lc|#{kA`rk1Lz8Fu=#CW!|Ox$!>Oa!{7;@>WvgB?2p*mocV!tt`4Pc<4wgJUSvOb_L^zSsb|Nf=bMvTZv09nTvn_ z+3PsmK8ym79f~3vP@C!$lBVONS}z775{9E1R(!D*!B7Gp?h6`f#W7ElO2cC}GcYl+ zW5VYASvo8C#ZoeH+u4K+m*&wwRuL?7!6~XPYK*_3zCuMYXjp3(1 zdmS79wg;!qD8_-lH0%x$i9{N*EI}p&g$^fNRsmf@LHJw(&OLoP-rYU~x6@+S9+$;} zw4{=(EcZ>=4gfSCR}T%xWt4eD1J)Uah~asj{}|N9U@yQ?0?xqsdEMuEq+|_l5h!(w zcxihO$qYd{lSP4pfKye>YA(SufB8Bp%bjT5GlbJGI2lWpHlnJ=XGlO#PZZBQw-+s2 zy3o-Xf-FOtAUQI zDb?Y$2wW&7vtq_3IqKS^!AJb$t-3S3b4rvHokfU=U=rEbIyJg8#W!rZ|?Xq z9{JOycyh}yI)+kk+Rex+GMdWF*t4S>9y5nnTE~pj8gY2@71*(R7~lTU+i=({hS6o< z$jo!p@3z8fHU${1#lfEVgw? zV1SY*8AB3Is&U}EXI0@yAcf8Q!?@%Jui%63Ebh5$B`j_WF1_q=boP!ylGD&MidD;} zVBf(AzPh3jr!A|;ZEN4af2`ku;$jb=GrU8Pkz`CSbka(n3tKw^-Ff?N&^*BihK@L% z++ADfy1`}P)%}5_P*mVO>f-W}&i*l@X+U!(A-ayS@EA%ftMSmE9>$_Yix5wx5tlQ_ zswxtZ2u6B#;l-z)#e)xTK#9i&tBrXY0*lpRco0e%D=D(7j%-GP-z8x2bT4X(?Z_w^ zdWPc2*gUxY$7iCtz>KZ$w4%W4!jc8$Mw$(73nG0HTz$)%cyUuVN{T&Db=|-T?D9Zx z6c^5_R=qZ>@W-tOehc8n{N}4iVV!}d=)nq~)kp!UYIGtRGj=}jfi}bMYCNI&Cg8nKWA7cn!}ye7^yd*o1`U7ey(oW*&BniKWl+VOIw4wWnY) z=Umyo7xo$%C?llE+heK&g&sS~ifxeOqX`pcwcOsY1hMxc2 zs_QgWQ)sulttJ}ID#U3$>IHKD>BQ%BCS%O=CEIGk>r zcm8VJd-poL{_-oZSnP<7jp6Wty%_HAhQlW2%3#O94EC8>ZHqu)bBc!au{~h0c51yn z7m_p9u~joW&UTt*loicz#W@g{!zP)m1h3Uh8HWS$4C6p+{&fbYCLm#c5v9YiZ2xd1 zi%PGZX0q}Gz{zWgk-cT`b1*QBQtzfmcG{?{QpD5Qenw-SR^#tPIGnSD`m};V!U{Aj z`8XN+a-4v4Is=sg>wdQ$RTbqp|B{RG;FFJot5}7N+owPgOEGiyG{}++ffvxx-ietD zPsYFh;@4kH5dmy4`utVSxCHpFVYvdctva3q8qZnzQ6&9l(f z(`{h<#-^#b>E`d_yWhVME5EV=JjcWB@u0h-9e3Sv2c&cw4GmLq|09pV?(-uQ8Z+GI z@scY4K+G|c!HugSxUqB3ZX7w%j)n>P`~w70~YUkclXGaMgs1ITjuJob@sx{31}{MBOH$#FJ(#0(ZCS?{>p18_LpM! z&YgJfzI$=WvPD?+rKKownqf9saq_84@bxuU;`HUqaneb%(bO~rQYH(l!-3m>c_-Wj zKD@m7ZEV=|ChmFQA=J;BhhQiSp3fPw+%e9E<1~y+7>wMtobAu0-^XG}%q?>pQuShM zFP^=5j?QR$Z&zpwqw(K!w0?vEz&lF~%QCyMpl{=@D4M(``qIx%LQ8)?tegc_jvB>F z=8HW}E8cqfZ@A&RSK=2pUW-#|{ct*M2q!WqZkmmit1p0P6;WJTh$6oabLKYZa&{&Y z(&-dx>Zf4soxg;MH3#qXQx;skJPUs=tNd5KAcev}6WD9^Kq z<^UW^iV7O+f(37M4C2{a=VQ9hOgHX{p}DDm<~@siGF(bnB%I3+i(L8G$HrNF*L^p>2^3AMoY^UjD;eQGZH)=k8zC^Sy(lBXe@+KG>)r(^kb~~;ZG1r zq@l>N!EBb)_^AHp#DdY!+#@TnSVSJdP!g&l;iffn@Y=dFpbsVS;!B5d{aF>Xr7gm> z9tyTI71_Lb^MojIljhY`jgqDUynFTfovkl*$)+35DOcD6)nF9Ax^50yo>+yt8XNkC z;vhUV3ZmO~?7)40ehT$7W@1WhB}RMO5E>i#NF9&qVqgCN60rmdeFgBB`(d-&CK$xr ztfVOD4+YU57{=w_yBfFs{(cB9A0pu>h^84%JJ(mh&c+#{v-%3mCJwzranw{h(DKw* z@vH0SLSyzs0B$^|QhBjkFj;UX@%GS@y3(%^2ODh9Ff3@JcKGA{`_J$uD|9rl=@xZpo8bl@D=&7^t5>x zP7Ao`8{a~IUk~aV8(2v~$%lv+%8-+1{`tkUB>q= zdk&>VPMA1~{*h5EUAzRuB%+|QfgwwLX~inT9x!$2>NpIGDB#@lRvVokdOpL%Y>$ES zRmC8j1w!n82C&A@&1N$KLqQ~yY3$g!9Y+ov#H%m-6%ReW8W&A9V{BU&oQ-8jnRz_? z@ILLXM_c*fjB+E-k>6^1F7tJ~juGKw7{G4iRPAS6o&PsaoH4ib_QH{vb<=$x(p4+# zh!#qs@7_0X@ttRYJh2j&t$r3W>xxlR?ni6uew0^MV&ki?LoiuEIRUHAJ0GcJ0+jH0 z>BWt(SY1fQgRtg+JS3e!w0lurRRx#J0lUo#tKEhpZS8Q_Y$z=+LnInSXmkuFfrDt` z(embIJowlOT(s1J{##y$wXOu3D}qfMyQnXga#lJ1+4jVgMWZ za~IymcLy=j*&l!Yfp6A+J=-^?Mz?ebPQL?ItBC$}Tal{r7=1m_lnj^EY&10;JlqDi z-OhRn@b<>1@!nf+qHkajGpY+wU+F{o(94)oSpuh(^-coGw1R=IUD&v>8;5&G(LNAG zZEZEA*eDjB{$*To^>v2s+no;d^z@*ks|zE6USz|i=>Oe5_(Y3gD5G22vBDv1_GzWU z<4<%y-yKYiJa^&26Sm0^oOp1QQc4JE`tkYG4*X(eIgt(z;ZRT^Ki(WfUxHvToQ2P8 zgVkh0B%L-gKvq#iCX+#JO&Qkx^l}{T8bD`H04|#u^)+Q^XsiR_%}}yQqfE{UDUoO# zW20eo^bQ$0!}{uC)K>Yi?%C~Fe9py2Q^nSn%{Z|AeQ22!x`zVDN)j3i+^F*acP{Z` zYLSUrra2u29sd*H0_)VJMQq-mXDIzx(28xT6UpUZ2)biMi8j4SkNqVz8E+1UWH` zY$OP$(}n$Qz4+ZDuVCFR-#}G)F-$BiFSCY96$ke1!>fPaf?8HsRTM0pHU;~n7!FAi zW>gg8t8-@Ii4XSUtQULH@~frvrL7_E*DtkS&DPA;{z*+z!Y42Q&#?(~%0pN0`|*Q2 z7v1`(>C*XS#oGMZGM)jW5~mfhG(k#ISmFVTrq?1gJc`kH3M$Qk)#qOQ01IZ;Ad!^N zSY3?Bz=0g*=W>9G!C2N7!&+QA2S;JEiP*Kj2VY&jz-UjiOA_W(`B7BvhtFZds3N1> z8^f}uT2M{KIkRf8yK6ve{$FqLvaCJ{;67$sjkSNm;PB}N$w5=>Y4$krm4t8}FoPgKk0L#w%EFu&F*llLK-_;L?MZ}k;*RvcyWY#N02v#*#pxNVM z@Oe+ZZ{Sn4r*WT#ct)$uPK!9g{q$>%tdItj-85uQ_Cy`JTLy-jH z3?6$ONTwu|6+2NJOd~QlY?QEh-UL;Z4VBO|6@XCS;> z7ChI^5*q47&yg-1h-M*Id*BlY_P6z5fA0w9u}qTNj_si+R6-2=Q0H-BFrLEJ&OTHX zy5aV+VhBjH$3-myw=&;5q5rE4AV!Zc;I47lXR}7Ev1pt-=Yq?yh4TckOq;w7Yj`YK7^T6zIqT6mpS5FXB$z_-(ILylNat2-9A#^(| zMxTJ_^kIKnA5xl%p+p8>n^ude@)Eo~6he1KF=7qTEaIe!V${^tWBID{kVwfK)pb&7 zcg$vM&aa*PD_(%)fx1iNczaS-F{X)Vef0ORHcrDCC(VJwWP-wRNXRO9n+0c1DaXF{ z9`qz+W-mErrA5M0cE^^+-1?g*CYb7w(3;Vdov1G8HqyP^&Tu{3zipHz)0OToWZi1B5d#M!DuRrp;XogF9X3S zcJAxI1!tX%)l|pJCLT7I-RKV*?>}IP5tb^OJ!1;a{^A_WY^=avUwz+rmaSkYl|iv6 z;7C^wE~_oZ{?RBFcpMNpAgd}6Bn^=iMR!^vRHu4EQdv2t4nY4WNdR}Ei}_wBHbHwz zW%Cj)BdOZ%NE#`az$qG{ii+*S5zO-0p-LG<(h>$zY$p`0++kyztiSyBZj1zj@VTv6 zbN(WuUcfK_3ujr7!_F01!sQn%LV??g;gJyj^PN4$`^%knEvcwle@a5!Xr;|yT7ezA zf-yuTl{GgaNjdBaCy~kOT3pt+xTYr9n)7Qvp&bxDfdMpcNr$46e8t{q?CBXE2j3G; zsQrmFZ{c~8P-HZ?oj4MTBaqA*4Gul&lwM(T@bwNGOgxXh9fNf5!5++SoiFT{P|odnUD&m+lkRH|pxW)^8k|;$;L)2(>eD<QtI1d*G7*LD5bzIZYkm81vj9F8hKF|!{+sD87R zjKq^%R@I5!Y(iX-m~?XyN!i+x4k}KwSgY%_`r!>9pt-4vYS|>&_Tf&9B`N05X)<2$ z;hr|M@7jjuS&fh-k^XtZHp1LvQc-(bhC|&}UNmVsJuN27x|KI`9pR+W@ZhtUb%nuH zN`;Y(By_}+eKAS-4uDOchWLL52^vfwz^1sQ%-a)=uKQp#8b1(;3j>)nFHw$&g1~}q zr8|-4bgDlSl`@NDRX=?!otbJj^ItjCGa{{j>K#&C;@4pH;`_HfjFC_jBcTYcy6s_5 z(TNg&nRf3}?~;Q(LCI`3uVl}%Ibu>~k*?FHyOL>^ELO}MM`cP#AS3aIBMITXkw|=J zD7r2tDf7rbLHuVKj0xkT8o&<$yldk5n9CwkkHtbcjt?5Y4Ls(4tJ(Y&0Lt`+D!TsW z3zZplCF&o3{vGYUpMOW2S?gEt{n16r^oB~Na*EYz+&l5k{C^+EaY2toq;88y&1NA6 zf_L+4sGd|l`$X_Fd63V*gj{)U5{D_^2T%imrFvQFIhTJwTH;}gQthI6MoN;_n@qyA z={0_=IDM{h$1Csb!l9lZD`p-NMgAH|(%#Ce=lCzE#^hnx zLFK3ZY3yVsPOhXM=YC^nPC2E5xv0wl`~|?C{K3vI&tIFk_n1LG=DUCCr?up>K6spY zCRdt2&h<8rqDJalZRh0^)Tm({+RHR8--=t}X!59M`z%w43dCz^G*M9H)|JAqlIpgu* zrpcI^Z5^LG`|SPif4$fG*4hgHACEIfAVK;?2I1T z8KrJ*d7&96`Io%cfB0{I^AC4E{?AV@Hcj)(#;BjRR^PkOi4X<`QD9-TmNm+{nXLuX zjl<^d6rt5GdaZtASh!Ewy7;NRaq-TocCK;tr~l1={-3{j<_z@NvvA)v2K?6P)8+%~ z>&-`h{%^hAYyT&^lN8oyXr(ZmWN^@%2UizzXjTSYzdGnDt(2)7XVSbhJLA;s zO;Q7RQ&yE3Cz(m}%6RXUhH`b?xX2>RtGXGdRZ~=Tm1Jd8Rqn$d{U<+mKZ&O%ycz~r zyvn13t6~$HagzJdw6ObE1~__P0Oh@J8rLijI$A4jCTS~W=e?K>xkq)Mqy>!8fQM)g z5B?rUf#!En)?RC66uQ(bGOt!uW4CuEKLp@;JaI|`8}7TtfG7x5QB~L(WjM5N5ho5W zp=lgCai~@XU0Hu;lv1s45jg+$%0ofJ!$WID9)RXSZq!YKrtzriM$xpo5&sU6<^_tf zLRJ>9dha}5^&-FPcM?^-*IG7RSmXB zDU!T`(F&c&f;`xpOys&WudueopHL{|a)ix2+Lg$IxByT20$ zPC;o(`TXTwoO^Loq}jMu#Yy1deG9l}|3J>s{`t+YNJ;ZUKf(LwA(C1_?byH6#fd|U zhy&xuoGaU-w|wHWpa0V7)2Ge)`g$8jUrP-*a|Y_{S@>W5g&#Sw-0K}+h#O6EHO(s& zMT4(iyov5C=F=1?O)^0njVs#|+P192^D0+}0)y4Xj+h2DfZidpMy?M6gK=7lshl{x z;`c9gO*gWSdw=G~9)G+Q!rdPFZf|_(+-cFCeiZ*ptyO=Tmd)01s_3|$ySjtbr9M^$ zT~Y7lg$`C0I#}w*k^pt10ILff>|g9*ZHb>Q7CNEm6Yahmg-Fv3pa1GbZ0(E?S}Tm& z9Hy8gxiQ-KL9g@v-~RBo9%U$GPV$;-zz5dXn~#6!LqX%VPSL~Hb)$#V0+T#JCkp5s zu{W9G;L4&9Re6uRti+kcffdy+YpT3MpG;v_HV1XntkPz)vQ{_uQh2X~@F%`@1OMYI z*KuuoD9*3V0U)1gUSc@SF-c1aca*L$CbaTll=RA=-;I$L3Y&XVF(o?COPgcd+?|rC zs%qT#aUYL8e)lP{zMGrY*H6oRru1))f?(jBbGzeAQHp^9u56`%iJ`Sc)i`lz9D<< zo1T5PO@o!zT}{u{G;VK_OUO#H5>|PXMTJm133-%0iIN9uElk;X<|;PF8H%b#FSL07 z58MY9h|CJP&g)McLhZen!-16nO-z2HvmB?DVilQJ^~%RT^uF#t{KW61zOAMFYnljE zO|vfyExMf;{Z1_L+FFYfhnHd1E*1v^bm9nEUWy7+)r-0jyRYgRmG}gWhmNmE-e8Qu zG%dvp$k#VLc<)Pz)BK@I$-hTQfqjed;96700MfgqLBARXSYr-DH+B;-g-)pPt~cEStC>j_=*EF$g*SFneE#d#g&B01MOn-B)RMfY zX7vKF)Qhk^OpzB&b6|h)+K0i%X5UNwPE8fB8$Tl=X!Vdzuad|h*DDXOKKI3|ICf|m@BY4H_~JLW@OxjpiZ6U~3lAJ#q-u+)P-~LBl$wBHa=G88*jZV7 zYQPIOhMPb4;SY}ajJr|)S6>Tgh}tsET^V$+IZ8!6dCYCTO@erx#@kLF#5XQausJI5 z@bMMge|SNDzh{3R457U^Kwy;+Psg`2Nks#gN6>+e(gM@0VC3*DAji{usMH6V+q2TU zAmQ$h;UqWTx-r7}t2+|=DKYWrv)^w8XVvEUG?7OiSjMS`*M#`8a-!yReg{{&QV20( zP@6_+F3ee3=n69ksIa7_->B+}=ox{u3taxxsZ+0QBJ{))?f2I%ZjRTM``8{O3{5SO z6nAR}ZAhD$08<>_wC4hf@0OOdH9}U`avimRIRdN1K`)X}I87Z!(@YX3YM25v*KrL# z$#vG&HvDVKfOE4FbZd9~wIs_CS*Y9!Trc)JlCpAP zngjo%y54z6cvKMdXp)f$ZL&lkK=8yGBjGR2OF@&E_%zNngI;W^s(R*nwe;r49@}uN zNM8*D^xfV_;#r5MMg5V@y+nCaSpK#jcMBY;YbRMEZ96X;45t}t z*9b0fb!&v{I}<65i>hG;=;P3;;WW#gSN~vRW1~KI?(V(+cN7r$sdqo}-}{~Ths&~V zlClyPC(ck(n%3!2qEzd-LnH$zeW?P)VYa@Y{c7uh77J{(VVGn%e{IVR)12D#$Jefn zSP9M>C$G5%DE{x*;^NY=dzXLd$qSeFKlS_-zdcDcot9Gi7XHVrG2AhXQIeE6HGs(t zf2N60?a3rMNjg8`0VJHJG|qCIyLT z%TH^jFn=DM>Xpu&J`Iia*}qN$o10J@8*Q=1Q;!1nDWHDffz_XL4Su~W8`RE8yk6?Y zB0VN9B!a}T{-0~aEI<0TgUn(EIQI|Zw7@XQB_ANC7*V3oV0jQU_aEykYt-*n_WX}e zCJ)%peeMEp{C~k>uV^mzmG-i}e%oSmM%;G1@64Izsk3MOk$aZk>Kb2<#+l2qIxLDB z+Y@2}67_F|yLlhr)=t_q;Z}H@e@i|q(~mChm3v3a%$=+oJghC=RoBhe(}{b0;QjW)SqZX(S(@if4tBUDPG8x)k8h!BARYQDh`TB(tVTEo*uQG+vx7`!31lYm<)v z>Oi++?^95Rv{r|`_xqW4lQ6>d1YEMb{``s8ZU5l<`mKoZQgMmdL7UmwK>fWB?*Fx3 zFa8BSEbiFWQ8ljh*k!%zyr0rJHMR5m*ZPOUFbI;QK$=zv0*!7r6yo!|f^QU71`$^G z8RUhBHbBMS3WhuZ6cC0+I>TJQE`XD-@7(ect)F0-+?w#*iE@4F)U}(5l+O&CWG*!@ z2o0gNCW#cK*l4hMd%>{KuwA@x~~!f32=n6)?kT zxvrzgt~8Bbk+?1}H#bv@%62*djA_$aVtd?lT?58?ktn@=RTC(ozhGTJ=g35;Mihk- z1=6g+c-&xV$)c`3a#y$JqkudoTt4d{*EAELD4UuHbQl;_6b(bMGFquHvL*<$CsV81 zKU-J$nAi5tUMXwTx7$8HU%$A0K8)0lCVBO{Mb%iPl#1iPMv>KQOw?@nHO?8M9l;P$ z<-p7|q-|#vWg|o@4MdR^;;dEjJ0C)g;<-377>!FzlLBF=VFQI}GSd#Uzf-HYCcn7` znapeX-R)RQ)1#75P`z%T`rTF#X;%B4oy^&3{n0Odb?al_{N~Ozaqf5c0Db#E&df~W zD_^;p91G0ZCM`#M<4gt@qR67(kCfJ0*R>-OMuv!lNG#VSLns+;mc)mFM%-y_xhz@8 z%;Wq^w%6@gOs56Xq=XGLc6V#Jj#^gmp4!c%&LN7{EO@YXAc(@X_?(4)3#v@h3g=(g z!_IE5kHnGM7n)D}$ioMI@0|~>JxtX<4GD|i&VbXWVa@{Xr{BDG;_toXo=^5vvvGgd z4q^{=V{58tyCk;T3FVs%A+c0p+8BXOZdB6QnG>QPp}!zwvb=Rr#zmBVlE#p*L*U=P@Z0gNF^>+t2PV_Y94 zSX=3%+X--EbApxSE;T~R*7-pKv#b(3=LT8GJaAtRm#$94EIM)9;2?3WhbNxuMQS9L zpKjd2@gouXeJzAh?ZvO$rnqxIkYx?dUD%O2fzf6m3UKVee%!m#N1YR-tuWl3B29}r z3XD|>`Lt}_`nj)dewEsO7I1b(y#E^yuN*0w<}-DQ1HHwl8u%t~J`gFt^?^gUxHZ9r z%{?hxn9Z?eH8+8|%{tfGt2Rm;pl^UAtuUFC=+ZVdo1Lze+)M_PD&u`qJLEJa1x%(j z78fJAjtH@!O1dR_9wOUf%HzO7A8$Chh6fMr7p7m@9b<2E8x6D0Jco4-gO0I#NmB(@ z$6cd;lve)4DHv?P*>k6%K+ovxuV|wV6jf7oiYjQBgKOgWw6z#N{Ll*CaA*b3Z;h~< zuy^aAgIPB*FE)G14?eZ)pI{ebAgnV=2jxtc1#!wNFNq?b8ND zO6`J_4hhK8MkFrt0t=y-MN&IqL{hcRO&X>&1t?gy)LBE!CdTF_#iMPo0xqrL8V|)x zG2jU$5c`DqL#G3`HzMcYno88)1To@;E}9}oywFFoJ>)XomoWge)vH%ce_+GdD5ZkH zn?E1$t$qK>nCR_j}dEvl*=hB~;`m5#B5%8Jt? zt?EA6a>5}(bnw67gN{t4L_H?*;UL(}4Mx<%xH-}@jHAy+KBHGbZ zh3rD2RO6A{*n{CYYlbglfaf(AE>!P-c6megNzQ% zO5_C_AyNVMclwA-AlYI+j?s%EpXPaWWp|`^rm1G>Y{Ssylc}%LLU)%2W-={kXOgds zlJbR@G2ro5TpV2+QnEGWh>onN zCHzr}Tp&$qM6m`7JpvyjDh%2%#`Tk&cwigX=^pspV&+`M9K2@-3X6UTj~=~8fYKlc zC1j>WiBXcMIIyAY)mXOcL};I|*%Ep4`TG0NjMyF#X2utqonLj{MW4c6%9*x85N+{<;Z>6l}FxB;| z3I6x=2HyF|A;ekGCM^^BrdkTdvZ}$Q8#ke>#|PmY6FFP%9IMOGVe*b@eVTB*amuD*D?37`c-^u`9>c zUmqhh9@^J1)}olESlG9OGUJ#_E)2-`En(M&D3cMwAi!Rl0ouYjHrdV1ft%*{&6C?oDxJa~Dw*$U5*f@^ONEJVn@v&|4WGjEO&L>|VWz zx~wDuq!pod1(sdL5GZjR+(ywT^kVz+EC_dCf+VqUS{jf8?IhU=s1Y1lg=rzJ?J+_N zO!mg`lt@{Ln$j^@Ll19|j#KoPLfFV7&**bz)ms=LDQk?gOs)^3SVHW0dk@{kMf8>; zj5c>*I4{)eA_#h@s|<0s-`Ww;DR!4qcAs_;Q{9b5N+d|qZjH)mcxk7RG>^tB6iu5} z=SA&IEw%fd3^+wh2w7P^zufC0^%j#tp&uFKv!bC3dSU; zvm8}c3In5UWIf#Mlu|WDwDyypE*K4 zN#QsoQP!Ad9F$YnhPbST$~37n2OE2t$WV5Y|cjSg8UE2>V- zmrxjfU#TYOdiuM3R7th;R$HW4e{KH3wav zn?dHxqrrhHGQO=QNi?q)v{=?`OYk`q!+VH!)J~&t7Nua04h!9mjMg(Ma40J;i-RgSzUiH8P;s%WY9+~V!>vfGYc}z~$EPBMfba#>hA;4CC+<6B>rjAPA(sA7vGoy^EQ_pmfwWhm~$y zDvFu97A8+CiSLrL%qvG85q$(P1xJqoZ!{q>&+ZV`+BV-y448}Ss@7Y&uD7&OM`%;t zdx0yWFh)jhN`gUkZxI}BpekF`meS#1qjRk4TFsfg10sb^UbE$3B&;$?p+jR1!MElb zZv-zu!E(BVz#$kIOl?TU=p{Xs(skww z=C8nUx}$FRnhec=>Pj1(jl|yK|Exni>)6rBGKBGG5=2aZ<92h{V=f9+s{zpcu#db>5 z*Uotzhk@qQMn8^jO@B=DLWt~jV)-5=tz+wifh;ORj?z79yT0wFXAcyxvyCPkiKnBI zwkq=u-C~MFgLus?o9Fx>BMEa6R*my1(smZnDWyE$ObY|+dr|NoTw{Lizdil**Kecs z|JodV+41O!6Gyf7Kd+Vg*}$45Za%Lnr?v7%+9NGEKkqck%MF|ZuG@wrizq4%wIs<4 zQB!Kgd|ak|Z)lDPwczRe`gz1@`wJ|4Y0rw!7ozz;HLgm8e%b#}?AOf%pT*j~{b^`WdCvPg-M6wzK_WLh>b%QQUA#2v5%6 z5RtdRXh(9cMQpgyK{pn2;WQX=0cs03E=^a?8Q69@eC})o6(VDAIbRmX5NQY2SeoN( z)aGgB{jdJng%>`>{SoYaY-7XSZu5T;2D~C89)0xDV5v+`rDgfSx^eHdTCdE<+j&UP zXkY)GOSJ9(ihpQle1)jl?BG0fpLom3V+U2K-wWV99(bEI`WS6kUWS}O zTd>vYH`!SR=Uh)N{{m-?_K-R^qQ} zY7Bo-X3)Ow?6khpb$^M^U-qD8N^9>fX^`Xp0HGYllmp4osQ>@~07*qoM6N<$f_N3j AiU0rr literal 8852 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 W3|Ch#yR>@%0000~Z|;#&e)ho!AN)Rm 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 diff --git a/resources/assets/edenmod/textures/gui/icons/tna.png b/resources/assets/edenmod/textures/gui/icons/tna.png index c1fcc84ef27ec59ddcae390853a62e3e3eafb064..d914b7e1d6adcad7c210eea4d267ca2046622faa 100644 GIT binary patch literal 5198 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 IM6N<$f_&ZC`2YX_ literal 8749 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 RJx~Au002ovPDHLkV1jInfX4s; literal 8410 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 From 7afb8441e5b8aa6530bd3f66e4b85ce0fe24fa66 Mon Sep 17 00:00:00 2001 From: burgerfan41 <87437687+burgerfan41@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:15:49 +0400 Subject: [PATCH 3/5] Send party size edits on Update instead of on every click Each +/- click on max size or filled slots went straight to the bot, re-rendering the Discord embed per click. They now go out once when Update is pressed, filled before max when the party shrinks so the bot doesn't reject the new max. A party update arriving meanwhile no longer overwrites the host's unsent edits. Also drops the stale 'auto-detected party size' wording from the create screen. --- src/tel/eden/mod/gui/PartyCreateScreen.java | 2 +- src/tel/eden/mod/gui/PartyManageScreen.java | 62 +++++++++++---------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/tel/eden/mod/gui/PartyCreateScreen.java b/src/tel/eden/mod/gui/PartyCreateScreen.java index 2259c5a..2581c76 100644 --- a/src/tel/eden/mod/gui/PartyCreateScreen.java +++ b/src/tel/eden/mod/gui/PartyCreateScreen.java @@ -16,7 +16,7 @@ import net.minecraft.resources.Identifier; import tel.eden.mod.EdenModClient; -/** Screen GUI for creating a raid/Annihilation/Festival/Other party with auto-detected party size. */ +/** Screen GUI for creating a raid/Annihilation/Festival/Other party. */ public final class PartyCreateScreen extends EdenReferenceScreen { private static final int BASE_PANEL_WIDTH = 350; private static final int BASE_PANEL_HEIGHT = 330; diff --git a/src/tel/eden/mod/gui/PartyManageScreen.java b/src/tel/eden/mod/gui/PartyManageScreen.java index 1b2a516..131e640 100644 --- a/src/tel/eden/mod/gui/PartyManageScreen.java +++ b/src/tel/eden/mod/gui/PartyManageScreen.java @@ -49,15 +49,18 @@ public PartyManageScreen(Screen parent, EdenModClient mod, PartyInfo party) { this.parent = parent; this.mod = mod; this.party = party; + this.filledSlots = filledCount(party); + this.maxSize = party.max(); + } - int fSlots = 0; + private static int filledCount(PartyInfo party) { + int filled = 0; for (String member : party.members()) { if (member.equals("*filled*")) { - fSlots++; + filled++; } } - this.filledSlots = fSlots; - this.maxSize = party.max(); + return filled; } @Override @@ -69,14 +72,16 @@ public void tick() { if (p.id() == party.id()) { found = true; if (!p.equals(party)) { + // Keep the host's unsent size edits; take the server's values for the rest. + boolean maxEdited = this.maxSize != party.max(); + boolean filledEdited = this.filledSlots != filledCount(party); this.party = p; - this.maxSize = p.max(); - int fSlots = 0; - for (String m : p.members()) { - if (m.equals("*filled*")) - fSlots++; + if (!maxEdited) { + this.maxSize = p.max(); + } + if (!filledEdited) { + this.filledSlots = filledCount(p); } - this.filledSlots = fSlots; String curNote = noteField != null ? noteField.getValue() : null; boolean noteFocused = noteField != null && noteField.isFocused(); @@ -259,29 +264,15 @@ private int realMembers() { private void adjustFilledSlots(int amount) { int limit = maxSize - realMembers(); - int next = Math.max(0, Math.min(limit, filledSlots + amount)); - if (next != filledSlots) { - filledSlots = next; - updateWidgetStates(); - var ws = mod != null ? mod.socket() : null; - if (ws != null) { - ws.sendPartyManage("filled", null, filledSlots, null); - } - } + filledSlots = Math.max(0, Math.min(limit, filledSlots + amount)); + updateWidgetStates(); } private void adjustMax(int amount) { int floor = Math.max(2, realMembers() + filledSlots); - int next = Math.max(floor, Math.min(10, maxSize + amount)); - if (next != maxSize) { - maxSize = next; - filledSlots = Math.min(filledSlots, Math.max(0, maxSize - realMembers())); - updateWidgetStates(); - var ws = mod != null ? mod.socket() : null; - if (ws != null) { - ws.sendPartyManage("max", null, maxSize, null); - } - } + maxSize = Math.max(floor, Math.min(10, maxSize + amount)); + filledSlots = Math.min(filledSlots, Math.max(0, maxSize - realMembers())); + updateWidgetStates(); } private void updateWidgetStates() { @@ -384,6 +375,19 @@ public void render(GuiGraphics g, int mouseX, int mouseY, float delta) { private void onUpdate() { var ws = mod != null ? mod.socket() : null; if (ws != null) { + // Size edits go out once here, not per click, so +/- doesn't re-render the Discord embed each time. + // A shrinking party drops its filled slots before its max, or the bot rejects the max as below occupancy. + boolean maxChanged = this.maxSize != party.max(); + boolean shrinking = this.maxSize < party.max(); + if (maxChanged && !shrinking) { + ws.sendPartyManage("max", null, this.maxSize, null); + } + if (this.filledSlots != filledCount(party)) { + ws.sendPartyManage("filled", null, this.filledSlots, null); + } + if (maxChanged && shrinking) { + ws.sendPartyManage("max", null, this.maxSize, null); + } if (noteField != null && !noteField.getValue().equals(party.note())) { ws.sendPartyManage("note", noteField.getValue(), 0, null); } From 699eb64ac79ed46373d09066ecc621d18564c145 Mon Sep 17 00:00:00 2001 From: Good627 <193092356+Good627@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:05:53 +0300 Subject: [PATCH 4/5] feat(party): Use 'wants to play' for Festival Minigames and add player list hover to party size --- src/tel/eden/mod/chat/PartyFormatter.java | 29 +++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/tel/eden/mod/chat/PartyFormatter.java b/src/tel/eden/mod/chat/PartyFormatter.java index 59703d0..00f552d 100644 --- a/src/tel/eden/mod/chat/PartyFormatter.java +++ b/src/tel/eden/mod/chat/PartyFormatter.java @@ -26,7 +26,9 @@ public static Component partyOpen(PartyInfo party) { if (party.ally()) { body.append(Component.literal("[Ally] ").withStyle(ChatFormatting.LIGHT_PURPLE)); } - body.append(Component.literal(party.host()).withStyle(ChatFormatting.AQUA)).append(Component.literal(" wants to raid ").withStyle(ChatFormatting.GREEN)).append(Component.literal(party.raid()).withStyle(ChatFormatting.YELLOW)).append(Component.literal(" (" + party.size() + "/" + party.max() + ") ").withStyle(ChatFormatting.GRAY)); + String action = "Festival Minigames".equalsIgnoreCase(party.raid()) ? " wants to play " : " wants to raid "; + Style countStyle = Style.EMPTY.withColor(ChatFormatting.GRAY).withHoverEvent(new HoverEvent.ShowText(playerListHover(party))); + body.append(Component.literal(party.host()).withStyle(ChatFormatting.AQUA)).append(Component.literal(action).withStyle(ChatFormatting.GREEN)).append(Component.literal(party.raid()).withStyle(ChatFormatting.YELLOW)).append(Component.literal(" (" + party.size() + "/" + party.max() + ") ").setStyle(countStyle)); if (party.world() != null && !party.world().isBlank()) { body.append(Component.literal("[" + party.world() + "] ").withStyle(ChatFormatting.DARK_AQUA)); } @@ -91,7 +93,8 @@ public static Component listing(List parties) { out.append(Component.literal("[Guild] ").withStyle(ChatFormatting.DARK_GREEN)); } out.append(Component.literal(" " + party.raid() + " ").withStyle(ChatFormatting.YELLOW)); - out.append(Component.literal(party.size() + "/" + party.max() + " | ").withStyle(ChatFormatting.GRAY)); + Style countStyle = Style.EMPTY.withColor(ChatFormatting.GRAY).withHoverEvent(new HoverEvent.ShowText(playerListHover(party))); + out.append(Component.literal(party.size() + "/" + party.max() + " | ").setStyle(countStyle)); if (party.world() != null && !party.world().isBlank()) { out.append(Component.literal("[" + party.world() + "] ").withStyle(ChatFormatting.DARK_AQUA)); } @@ -117,4 +120,26 @@ private static Component joinButton(int id) { Style style = Style.EMPTY.withColor(ChatFormatting.GREEN).withUnderlined(true).withClickEvent(new ClickEvent.RunCommand("/eden party join " + id)).withHoverEvent(new HoverEvent.ShowText(Component.literal("Click to join party #" + id))); return Component.literal("[JOIN #" + id + "]").setStyle(style); } + + private static Component playerListHover(PartyInfo party) { + MutableComponent tooltip = Component.literal("Party Members (" + party.size() + "/" + party.max() + "):").withStyle(ChatFormatting.GOLD); + for (int i = 0; i < party.size(); i++) { + tooltip.append("\n"); + String raw = party.members().get(i); + if ("*filled*".equals(raw)) { + tooltip.append(Component.literal("• ").withStyle(ChatFormatting.DARK_GRAY)).append(Component.literal("*filled slot*").withStyle(ChatFormatting.GRAY)); + } else { + String display = party.displayMember(i); + tooltip.append(Component.literal("• ").withStyle(ChatFormatting.DARK_GRAY)).append(Component.literal(display).withStyle(i == 0 ? ChatFormatting.AQUA : ChatFormatting.WHITE)); + if (i == 0) { + tooltip.append(Component.literal(" (Host)").withStyle(ChatFormatting.YELLOW)); + } + } + } + int open = party.max() - party.size(); + if (open > 0) { + tooltip.append("\n").append(Component.literal("+ " + open + " open slot" + (open == 1 ? "" : "s")).withStyle(ChatFormatting.DARK_GRAY)); + } + return tooltip; + } } From 0c40c70fb351ab76d0d3bd6d349bac901b1017ef Mon Sep 17 00:00:00 2001 From: Good627 <193092356+Good627@users.noreply.github.com> Date: Tue, 27 Oct 2026 14:24:05 +0300 Subject: [PATCH 5/5] fix(party): Prevent 20Hz sort loop in list screen, align filled slots limit, and improve head fetch retry --- src/tel/eden/mod/gui/PartyCreateScreen.java | 12 ++++++-- src/tel/eden/mod/gui/PartyListScreen.java | 6 ++-- src/tel/eden/mod/gui/PartyManageScreen.java | 34 +++++++++++++++------ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/tel/eden/mod/gui/PartyCreateScreen.java b/src/tel/eden/mod/gui/PartyCreateScreen.java index 2581c76..c20731e 100644 --- a/src/tel/eden/mod/gui/PartyCreateScreen.java +++ b/src/tel/eden/mod/gui/PartyCreateScreen.java @@ -165,6 +165,7 @@ private void trimToMax() { break; } } + filledSlots = Math.min(filledSlots, maxAllowedFilledSlots()); } private void toggleSendToAlly() { @@ -229,8 +230,12 @@ private void adjustMaxPartySize(int amount) { } } + private int maxAllowedFilledSlots() { + return Math.max(0, maxPartySize - 2 - addedMembers.size()); + } + private void adjustFilledSlots(int amount) { - int maxAllowed = Math.max(0, maxPartySize - 1 - addedMembers.size()); + int maxAllowed = maxAllowedFilledSlots(); filledSlots = Math.max(0, Math.min(maxAllowed, filledSlots + amount)); updateWidgetStates(); } @@ -243,6 +248,7 @@ private void onAddPlayer() { if (occupancy() < maxPartySize) { addedMembers.add(ign); addPlayerField.setValue(""); + trimToMax(); updateWidgetStates(); } } @@ -263,7 +269,7 @@ private String getCreateDisabledReason() { if (selectedTargets.contains("Festival Minigames") && (mod != null && !mod.isFestivalEnabled())) { return "Festival Minigames party finder is currently disabled by Senate."; } - int limit = Math.max(0, maxPartySize - 2 - addedMembers.size()); + int limit = maxAllowedFilledSlots(); if (filledSlots > limit) { return "Filled slots must be between 0 and " + limit + "."; } @@ -286,7 +292,7 @@ private void updateWidgetStates() { btnMaxPlus.active = canEditMax && maxPartySize < 10; btnFilledMinus.active = filledSlots > 0; - btnFilledPlus.active = occupancy() < maxPartySize; + btnFilledPlus.active = filledSlots < maxAllowedFilledSlots(); if (btnAddPlayer != null) { btnAddPlayer.active = occupancy() < maxPartySize; diff --git a/src/tel/eden/mod/gui/PartyListScreen.java b/src/tel/eden/mod/gui/PartyListScreen.java index 998aabc..34a20f7 100644 --- a/src/tel/eden/mod/gui/PartyListScreen.java +++ b/src/tel/eden/mod/gui/PartyListScreen.java @@ -20,6 +20,7 @@ public final class PartyListScreen extends EdenReferenceScreen { private final EdenModClient mod; private final List lastKnownParties = new ArrayList<>(); + private List rawKnownParties = List.of(); private EdenPanelLayout layout; private int scrollOffset; @@ -51,8 +52,9 @@ private void requestPartyList() { } private void refreshPartySnapshot() { + rawKnownParties = mod != null ? mod.knownParties() : List.of(); lastKnownParties.clear(); - lastKnownParties.addAll(mod.knownParties()); + lastKnownParties.addAll(rawKnownParties); lastKnownParties.sort(java.util.Comparator.comparing(PartyListScreen::isFull)); scrollOffset = Math.max(0, Math.min(scrollOffset, Math.max(0, lastKnownParties.size() - VISIBLE_ROWS))); } @@ -64,7 +66,7 @@ private static boolean isFull(PartyInfo party) { @Override public void tick() { super.tick(); - if (!lastKnownParties.equals(mod.knownParties())) { + if (mod != null && !rawKnownParties.equals(mod.knownParties())) { refreshPartySnapshot(); } } diff --git a/src/tel/eden/mod/gui/PartyManageScreen.java b/src/tel/eden/mod/gui/PartyManageScreen.java index 131e640..21e80f6 100644 --- a/src/tel/eden/mod/gui/PartyManageScreen.java +++ b/src/tel/eden/mod/gui/PartyManageScreen.java @@ -42,6 +42,8 @@ public final class PartyManageScreen extends EdenReferenceScreen { private Identifier iconTarget; private static final Map HEAD_CACHE = new java.util.concurrent.ConcurrentHashMap<>(); private static final Set HEAD_FETCHING = java.util.concurrent.ConcurrentHashMap.newKeySet(); + private static final Map HEAD_FAILURES = new java.util.concurrent.ConcurrentHashMap<>(); + private static final long HEAD_RETRY_DELAY_MS = 60_000L; private EdenPanelLayout layout; public PartyManageScreen(Screen parent, EdenModClient mod, PartyInfo party) { @@ -322,8 +324,9 @@ public void render(GuiGraphics g, int mouseX, int mouseY, float delta) { Identifier headId = HEAD_CACHE.get(member); if (headId == null) { g.fill(layout.x(10), yPos - 1, layout.x(10) + headSize, yPos - 1 + headSize, 0xFF444444); - if (!HEAD_FETCHING.contains(member)) { - HEAD_FETCHING.add(member); + Long lastFail = HEAD_FAILURES.get(member); + boolean canRetry = lastFail == null || (System.currentTimeMillis() - lastFail > HEAD_RETRY_DELAY_MS); + if (canRetry && HEAD_FETCHING.add(member)) { java.util.concurrent.CompletableFuture.runAsync(() -> fetchHead(member)); } } else { @@ -438,21 +441,34 @@ public boolean mouseScrolled(double mouseX, double mouseY, double d, double e) { private void fetchHead(String member) { if (!member.matches("[A-Za-z0-9_]{1,16}")) return; + NativeImage img = null; try { - java.net.URLConnection conn = TlsPolicy.open(new java.net.URL("https://minotar.net/helm/" + member + "/12.png")); + java.net.URLConnection conn = TlsPolicy.open(java.net.URI.create("https://minotar.net/helm/" + member + "/12.png").toURL()); conn.setConnectTimeout(5_000); conn.setReadTimeout(5_000); try (java.io.InputStream stream = conn.getInputStream()) { - var img = NativeImage.read(stream); - Minecraft.getInstance().execute(() -> { + img = NativeImage.read(stream); + } + final NativeImage finalImg = img; + Minecraft.getInstance().execute(() -> { + try { Identifier loc = Identifier.parse("edenmod:head/" + member.toLowerCase()); - var texture = new DynamicTexture(() -> member, img); + var texture = new DynamicTexture(() -> member, finalImg); Minecraft.getInstance().getTextureManager().register(loc, texture); HEAD_CACHE.put(member, loc); - }); - } + HEAD_FAILURES.remove(member); + } catch (Exception e) { + finalImg.close(); + } finally { + HEAD_FETCHING.remove(member); + } + }); } catch (Exception e) { - // Silently fail if head cannot be fetched + if (img != null) { + img.close(); + } + HEAD_FAILURES.put(member, System.currentTimeMillis()); + HEAD_FETCHING.remove(member); } } }