added fav and some other stuff
This commit is contained in:
60
CURSEFORGE_DESCRIPTION.md
Normal file
60
CURSEFORGE_DESCRIPTION.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Vault Party UI
|
||||
|
||||
Vault Party UI is a client-side Forge mod for Vault Hunters 1.18.2 that adds a fast, focused party management screen. It reduces chat command spam and keeps the most common party actions in one place.
|
||||
|
||||
## Features
|
||||
|
||||
* Open a party UI with a keybind instead of typing `/party` commands by hand.
|
||||
* Create, leave, and disband parties.
|
||||
* Invite nearby players, invite everyone nearby, or invite favorites.
|
||||
* Handle incoming party invites with accept and decline buttons.
|
||||
* Keep track of current party members and online players in one screen.
|
||||
* Invite or remove players directly from the player list.
|
||||
* Mark favorite players with a star and send favorite-only invites.
|
||||
* Use an auto-accept invites toggle for faster party joins.
|
||||
|
||||
## Why use it?
|
||||
|
||||
If you play Vault Hunters with friends, this mod makes party management much less annoying. Instead of constantly typing commands, you get a dedicated UI for the most common party actions, plus favorites and auto-accept to make repeated party setup faster.
|
||||
|
||||
## Requirements
|
||||
|
||||
* Minecraft 1.18.2
|
||||
* Forge 40.x
|
||||
* Vault Hunters modpack
|
||||
|
||||
## Notes
|
||||
|
||||
* This mod is client-side only.
|
||||
* The server still controls party permissions and final command behavior.
|
||||
* For best results, test it inside the Vault Hunters pack client.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.2.2
|
||||
|
||||
* Removed the `Runtime.getRuntime()` fallback from the external link handler.
|
||||
|
||||
### 1.2.1
|
||||
|
||||
* Fixed the accept and decline invite buttons so they sit correctly around the Create Party button.
|
||||
|
||||
### 1.2.0
|
||||
|
||||
* Added favorite players with a clickable star in the online player list.
|
||||
* Added Invite Favorites to invite every available favorite player at once.
|
||||
* Reworked the party screen layout to better fit the top controls and both list panels.
|
||||
* Improved the offline player label shown in the party list.
|
||||
|
||||
### 1.1.0
|
||||
|
||||
* Added auto-accept invites toggle that works even when the screen is closed.
|
||||
* Added player heads beside party and online player names.
|
||||
* Improved row states, invite cooldown handling, and inline UI feedback.
|
||||
* Refined layout spacing and removed the extra filter button/context text.
|
||||
* Split party screen logic into helper classes to keep the screen file smaller.
|
||||
|
||||
### 1.0.0
|
||||
|
||||
* Initial release of Vault Party UI.
|
||||
* Added the party management screen, keybind, and core invite/remove actions.
|
||||
29
GITHUB_RELEASE_1.2.2.md
Normal file
29
GITHUB_RELEASE_1.2.2.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Vault Party UI 1.2.2
|
||||
|
||||
## Highlights
|
||||
|
||||
- Removed the `Runtime.getRuntime()` fallback from the external link handler.
|
||||
- Kept the project branding as Vault Party UI for CurseForge-friendly naming.
|
||||
- Preserved the current party UI improvements, including favorites, invite handling, and the layout updates around the Create Party button.
|
||||
|
||||
## Notes
|
||||
|
||||
- This is a client-side Forge mod for Vault Hunters 1.18.2.
|
||||
- Built for Forge 40.x.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.2.2
|
||||
|
||||
- Removed the `Runtime.getRuntime()` fallback from the external link handler.
|
||||
|
||||
### 1.2.1
|
||||
|
||||
- Fixed the accept and decline invite buttons so they sit correctly around the Create Party button.
|
||||
|
||||
### 1.2.0
|
||||
|
||||
- Added favorite players with a clickable star in the online player list.
|
||||
- Added Invite Favorites to invite every available favorite player at once.
|
||||
- Reworked the party screen layout to better fit the top controls and both list panels.
|
||||
- Improved the offline player label shown in the party list.
|
||||
15
README.md
15
README.md
@@ -13,6 +13,21 @@ Client-side Forge mod for Vault Hunters (Minecraft 1.18.2) that provides a party
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.2.2
|
||||
|
||||
- Removed Runtime.getRuntime() fallback.
|
||||
|
||||
### 1.2.1
|
||||
|
||||
- Fixed the accept and decline invite buttons so they sit correctly around the Create Party button.
|
||||
|
||||
### 1.2.0
|
||||
|
||||
- Added favorite players with a clickable star in the online player list.
|
||||
- Added Invite Favorites to invite every available favorite player at once.
|
||||
- Reworked the party screen layout to better fit the top controls and both list panels.
|
||||
- Improved the offline player label shown in the party list.
|
||||
|
||||
### 1.1.0
|
||||
|
||||
- Added auto-accept invites toggle that works even when the screen is closed.
|
||||
|
||||
@@ -8,5 +8,5 @@ mapping_version=1.18.2
|
||||
|
||||
mod_id=vaultpartyui
|
||||
mod_name=Vault Party UI
|
||||
mod_version=1.1.0
|
||||
mod_version=1.2.2
|
||||
mod_group_id=dev.massuus.vaultpartyui
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package dev.massuus.vaultpartyui.client;
|
||||
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ClientFavoritePlayers {
|
||||
private static final String FILE_NAME = "vaultpartyui-favorites.txt";
|
||||
private static final Set<UUID> FAVORITES = new LinkedHashSet<>();
|
||||
|
||||
private static boolean loaded;
|
||||
|
||||
private ClientFavoritePlayers() {
|
||||
}
|
||||
|
||||
public static boolean isFavorite(UUID playerId) {
|
||||
ensureLoaded();
|
||||
return playerId != null && FAVORITES.contains(playerId);
|
||||
}
|
||||
|
||||
public static void toggleFavorite(UUID playerId) {
|
||||
if (playerId == null) {
|
||||
return;
|
||||
}
|
||||
setFavorite(playerId, !isFavorite(playerId));
|
||||
}
|
||||
|
||||
public static void setFavorite(UUID playerId, boolean favorite) {
|
||||
if (playerId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureLoaded();
|
||||
if (favorite) {
|
||||
FAVORITES.add(playerId);
|
||||
} else {
|
||||
FAVORITES.remove(playerId);
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
private static void ensureLoaded() {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
loaded = true;
|
||||
|
||||
Path file = favoritesFile();
|
||||
if (file == null || !Files.exists(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
for (String line : lines) {
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
FAVORITES.add(UUID.fromString(trimmed));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void save() {
|
||||
Path file = favoritesFile();
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Path parent = file.getParent();
|
||||
if (parent != null) {
|
||||
Files.createDirectories(parent);
|
||||
}
|
||||
Files.write(file, FAVORITES.stream().map(UUID::toString).toList());
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static Path favoritesFile() {
|
||||
return FMLPaths.CONFIGDIR.get().resolve(FILE_NAME);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ package dev.massuus.vaultpartyui.client.screen;
|
||||
final class OnlineRow {
|
||||
final OnlinePlayer player;
|
||||
final RowState state;
|
||||
final boolean favorite;
|
||||
|
||||
OnlineRow(OnlinePlayer player, RowState state) {
|
||||
OnlineRow(OnlinePlayer player, RowState state, boolean favorite) {
|
||||
this.player = player;
|
||||
this.state = state;
|
||||
this.favorite = favorite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.massuus.vaultpartyui.client.screen;
|
||||
|
||||
import dev.massuus.vaultpartyui.client.ClientFavoritePlayers;
|
||||
import iskallia.vault.client.data.ClientPartyData;
|
||||
import iskallia.vault.world.data.VaultPartyData.Party;
|
||||
|
||||
@@ -32,7 +33,7 @@ final class PartyRosterService {
|
||||
if (filterMode == FilterMode.OTHER_PARTY && state != RowState.OTHER_PARTY) {
|
||||
continue;
|
||||
}
|
||||
rows.add(new OnlineRow(player, state));
|
||||
rows.add(new OnlineRow(player, state, ClientFavoritePlayers.isFavorite(player.id)));
|
||||
}
|
||||
|
||||
rows.sort((a, b) -> {
|
||||
|
||||
@@ -3,6 +3,7 @@ package dev.massuus.vaultpartyui.client.screen;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.massuus.vaultpartyui.client.ClientFavoritePlayers;
|
||||
import dev.massuus.vaultpartyui.client.ClientPartySettings;
|
||||
import iskallia.vault.client.data.ClientPartyData;
|
||||
import iskallia.vault.client.data.ClientPartyInviteState;
|
||||
@@ -36,14 +37,15 @@ public class PartyScreen extends Screen {
|
||||
private static final int BUTTON_WIDTH = 90;
|
||||
private static final int BUTTON_HEIGHT = 20;
|
||||
private static final int BUTTON_GAP = 4;
|
||||
private static final int PANEL_TOP = 124;
|
||||
private static final int PANEL_HEIGHT = 155;
|
||||
private static final int PANEL_TOP = 58;
|
||||
private static final int PANEL_HEIGHT = 246;
|
||||
private static final int PANEL_PADDING = 10;
|
||||
private static final int ONLINE_ROW_HEIGHT = 14;
|
||||
private static final int VISIBLE_ONLINE_ROWS = 8;
|
||||
private static final int VISIBLE_ONLINE_ROWS = 15;
|
||||
private static final int HEAD_SIZE = 8;
|
||||
private static final long INVITE_COOLDOWN_MS = 8000L;
|
||||
private static final int STATE_REFRESH_INTERVAL_TICKS = 4;
|
||||
private static final int STAR_SIZE = 8;
|
||||
|
||||
private final Screen parentScreen;
|
||||
|
||||
@@ -55,6 +57,7 @@ public class PartyScreen extends Screen {
|
||||
private Button disbandPartyButton;
|
||||
private Button inviteNearbyButton;
|
||||
private Button inviteAllButton;
|
||||
private Button inviteFavoritesButton;
|
||||
private Button acceptInviteButton;
|
||||
private Button declineInviteButton;
|
||||
private Button autoAcceptToggleButton;
|
||||
@@ -74,31 +77,34 @@ public class PartyScreen extends Screen {
|
||||
super.init();
|
||||
rebuildState();
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int rowWidth = BUTTON_WIDTH * 3 + BUTTON_GAP * 2;
|
||||
int rowX = centerX - rowWidth / 2;
|
||||
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||
int leftPanelX = 20;
|
||||
int rightPanelX = leftPanelX + panelWidth + PANEL_PADDING;
|
||||
int leftRowWidth = BUTTON_WIDTH * 2 + BUTTON_GAP;
|
||||
int rightRowWidth = BUTTON_WIDTH * 3 + BUTTON_GAP * 2;
|
||||
int leftRowX = leftPanelX + panelWidth / 2 - leftRowWidth / 2;
|
||||
int rightRowX = rightPanelX + panelWidth / 2 - rightRowWidth / 2;
|
||||
|
||||
this.createPartyButton = addRenderableWidget(new Button(rowX, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.create"), button -> sendPartyCommand("party create")));
|
||||
this.leavePartyButton = addRenderableWidget(new Button(rowX + BUTTON_WIDTH + BUTTON_GAP, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.leave"), button -> sendPartyCommand("party leave")));
|
||||
this.disbandPartyButton = addRenderableWidget(new Button(rowX + (BUTTON_WIDTH + BUTTON_GAP) * 2, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.disband"), button -> sendPartyCommand("party disband")));
|
||||
int createX = this.width / 2 - BUTTON_WIDTH / 2;
|
||||
this.createPartyButton = addRenderableWidget(new Button(createX, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.create"), button -> sendPartyCommand("party create")));
|
||||
this.leavePartyButton = addRenderableWidget(new Button(leftRowX, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.leave"), button -> sendPartyCommand("party leave")));
|
||||
this.disbandPartyButton = addRenderableWidget(new Button(leftRowX + BUTTON_WIDTH + BUTTON_GAP, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.disband"), button -> sendPartyCommand("party disband")));
|
||||
|
||||
this.inviteNearbyButton = addRenderableWidget(new Button(centerX - BUTTON_WIDTH - (BUTTON_GAP / 2), 62, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_nearby"), button -> sendPartyCommand("party invite nearby")));
|
||||
this.inviteAllButton = addRenderableWidget(new Button(centerX + (BUTTON_GAP / 2), 62, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_all"), button -> sendPartyCommand("party invite all")));
|
||||
this.inviteNearbyButton = addRenderableWidget(new Button(rightRowX, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_nearby"), button -> sendPartyCommand("party invite nearby")));
|
||||
this.inviteAllButton = addRenderableWidget(new Button(rightRowX + BUTTON_WIDTH + BUTTON_GAP, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_all"), button -> sendPartyCommand("party invite all")));
|
||||
this.inviteFavoritesButton = addRenderableWidget(new Button(rightRowX + (BUTTON_WIDTH + BUTTON_GAP) * 2, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_favorites"), button -> inviteFavoritePlayers()));
|
||||
|
||||
int inviteButtonWidth = 140;
|
||||
int inviteButtonX = centerX - inviteButtonWidth - 4;
|
||||
int declineButtonX = centerX + 4;
|
||||
this.acceptInviteButton = addRenderableWidget(new Button(inviteButtonX, 90, inviteButtonWidth, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.accept_invite"), button -> acceptPendingInvite()));
|
||||
this.declineInviteButton = addRenderableWidget(new Button(declineButtonX, 90, inviteButtonWidth, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.decline_invite"), button -> declinePendingInvite()));
|
||||
this.autoAcceptToggleButton = addRenderableWidget(new Button(centerX - 95, 90, 190, BUTTON_HEIGHT, autoAcceptToggleLabel(), button -> {
|
||||
// Position invite accept/decline to the left/right of the centered Create Party button, same vertical level
|
||||
this.acceptInviteButton = addRenderableWidget(new Button(createX - inviteButtonWidth - BUTTON_GAP, 34, inviteButtonWidth, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.accept_invite"), button -> acceptPendingInvite()));
|
||||
this.declineInviteButton = addRenderableWidget(new Button(createX + BUTTON_WIDTH + BUTTON_GAP, 34, inviteButtonWidth, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.decline_invite"), button -> declinePendingInvite()));
|
||||
this.autoAcceptToggleButton = addRenderableWidget(new Button(20, this.height - BUTTON_HEIGHT - 8, 190, BUTTON_HEIGHT, autoAcceptToggleLabel(), button -> {
|
||||
ClientPartySettings.toggleAutoAcceptInvites();
|
||||
updateAutoAcceptToggleLabel();
|
||||
pushToast(new TranslatableComponent(ClientPartySettings.isAutoAcceptInvitesEnabled() ? "screen.vaultpartyui.toast_auto_accept_on" : "screen.vaultpartyui.toast_auto_accept_off"), 0xE3C38C);
|
||||
}));
|
||||
|
||||
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||
int targetBoxWidth = panelWidth - PANEL_PADDING * 2;
|
||||
int rightPanelX = 20 + panelWidth + PANEL_PADDING;
|
||||
this.targetBox = new EditBox(this.font, rightPanelX + PANEL_PADDING, PANEL_TOP + 18, targetBoxWidth, 20, new TranslatableComponent("screen.vaultpartyui.target"));
|
||||
this.targetBox.setMaxLength(64);
|
||||
addRenderableWidget(this.targetBox);
|
||||
@@ -192,7 +198,7 @@ public class PartyScreen extends Screen {
|
||||
int creditH = 10;
|
||||
if (mouseX >= creditX && mouseX <= creditX + creditW && mouseY >= creditY && mouseY <= creditY + creditH) {
|
||||
try {
|
||||
openUrl("https://github.com/massuus/vault-hunters-party-ui");
|
||||
openUrl("https://github.com/massuus/vault-party-ui");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return true;
|
||||
@@ -391,11 +397,23 @@ public class PartyScreen extends Screen {
|
||||
|
||||
private void updateActionVisibility() {
|
||||
boolean inParty = isLocalPlayerInParty();
|
||||
int centerX = this.width / 2;
|
||||
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||
int leftPanelX = 20;
|
||||
int rightPanelX = leftPanelX + panelWidth + PANEL_PADDING;
|
||||
|
||||
if (this.createPartyButton != null) {
|
||||
this.createPartyButton.visible = !inParty;
|
||||
this.createPartyButton.x = centerX - (BUTTON_WIDTH / 2);
|
||||
int createX = this.width / 2 - (BUTTON_WIDTH / 2);
|
||||
this.createPartyButton.x = createX;
|
||||
// keep accept/decline positioned relative to create button
|
||||
if (this.acceptInviteButton != null) {
|
||||
this.acceptInviteButton.x = createX - (this.acceptInviteButton.getWidth() + BUTTON_GAP);
|
||||
this.acceptInviteButton.y = this.createPartyButton.y;
|
||||
}
|
||||
if (this.declineInviteButton != null) {
|
||||
this.declineInviteButton.x = createX + BUTTON_WIDTH + BUTTON_GAP;
|
||||
this.declineInviteButton.y = this.createPartyButton.y;
|
||||
}
|
||||
}
|
||||
if (this.leavePartyButton != null) {
|
||||
this.leavePartyButton.visible = inParty;
|
||||
@@ -404,10 +422,9 @@ public class PartyScreen extends Screen {
|
||||
this.disbandPartyButton.visible = inParty;
|
||||
}
|
||||
|
||||
// Keep the first action row centered for the currently visible controls.
|
||||
if (inParty && this.leavePartyButton != null && this.disbandPartyButton != null) {
|
||||
int rowWidth = BUTTON_WIDTH * 2 + BUTTON_GAP;
|
||||
int rowX = centerX - rowWidth / 2;
|
||||
int rowX = leftPanelX + panelWidth / 2 - rowWidth / 2;
|
||||
this.leavePartyButton.x = rowX;
|
||||
this.disbandPartyButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
||||
}
|
||||
@@ -418,16 +435,22 @@ public class PartyScreen extends Screen {
|
||||
if (this.inviteAllButton != null) {
|
||||
this.inviteAllButton.visible = inParty;
|
||||
}
|
||||
if (this.inviteFavoritesButton != null) {
|
||||
this.inviteFavoritesButton.visible = inParty;
|
||||
this.inviteFavoritesButton.active = inParty && hasInviteableFavorites();
|
||||
}
|
||||
if (this.autoAcceptToggleButton != null) {
|
||||
this.autoAcceptToggleButton.visible = true;
|
||||
this.autoAcceptToggleButton.active = true;
|
||||
this.autoAcceptToggleButton.x = 20;
|
||||
this.autoAcceptToggleButton.y = this.height - BUTTON_HEIGHT - 8;
|
||||
}
|
||||
// Keep the second action row centered as a pair.
|
||||
if (this.inviteNearbyButton != null && this.inviteAllButton != null) {
|
||||
int rowWidth = BUTTON_WIDTH * 2 + BUTTON_GAP;
|
||||
int rowX = centerX - rowWidth / 2;
|
||||
if (this.inviteNearbyButton != null && this.inviteAllButton != null && this.inviteFavoritesButton != null) {
|
||||
int rowWidth = BUTTON_WIDTH * 3 + BUTTON_GAP * 2;
|
||||
int rowX = rightPanelX + panelWidth / 2 - rowWidth / 2;
|
||||
this.inviteNearbyButton.x = rowX;
|
||||
this.inviteAllButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
||||
this.inviteFavoritesButton.x = rowX + (BUTTON_WIDTH + BUTTON_GAP) * 2;
|
||||
}
|
||||
|
||||
updateInviteButtons();
|
||||
@@ -487,7 +510,8 @@ public class PartyScreen extends Screen {
|
||||
private void renderOnlinePanel(PoseStack poseStack, int panelX, int panelWidth, int mouseX, int mouseY) {
|
||||
int textX = panelX + 10;
|
||||
int listTop = PANEL_TOP + 48;
|
||||
int listHeight = VISIBLE_ONLINE_ROWS * ONLINE_ROW_HEIGHT + 6;
|
||||
int panelBottom = PANEL_TOP + PANEL_HEIGHT;
|
||||
int listHeight = Math.min(VISIBLE_ONLINE_ROWS * ONLINE_ROW_HEIGHT + 6, Math.max(0, panelBottom - listTop - 8));
|
||||
|
||||
fill(poseStack, panelX + 8, PANEL_TOP + 20, panelX + panelWidth - 8, PANEL_TOP + 42, 0xAA1A1A1A);
|
||||
this.font.draw(poseStack, new TranslatableComponent("screen.vaultpartyui.target").getString(), textX, PANEL_TOP + 24, 0xA0A0A0);
|
||||
@@ -522,16 +546,30 @@ public class PartyScreen extends Screen {
|
||||
|
||||
// draw player name and per-row action (invite/remove)
|
||||
this.fill(poseStack, panelX + 10, rowY - 2, panelX + panelWidth - 10, rowY + ONLINE_ROW_HEIGHT - 2, background);
|
||||
drawPlayerHead(poseStack, player.id, panelX + 12, rowY);
|
||||
this.font.draw(poseStack, player.name, panelX + 12 + HEAD_SIZE + 4, rowY, RowPresentation.nameColor(row.state));
|
||||
int starX = panelX + 12;
|
||||
int starColor = row.favorite ? 0xFFD76A : 0xB0B0B0;
|
||||
boolean starHovered = isFavoriteToggleHovered(mouseX, mouseY, starX, rowY);
|
||||
if (starHovered) {
|
||||
starColor = 0xFFFFFF;
|
||||
}
|
||||
this.font.draw(poseStack, row.favorite ? "\u2605" : "\u2606", starX, rowY, starColor);
|
||||
|
||||
drawPlayerHead(poseStack, player.id, starX + STAR_SIZE + 4, rowY);
|
||||
|
||||
int actionX = panelX + panelWidth - 110;
|
||||
int nameX = starX + STAR_SIZE + 4 + HEAD_SIZE + 4;
|
||||
int nameWidth = Math.max(0, actionX - nameX - 8);
|
||||
String displayName = this.font.plainSubstrByWidth(player.name, nameWidth);
|
||||
this.font.draw(poseStack, displayName, nameX, rowY, RowPresentation.nameColor(row.state));
|
||||
|
||||
Component action = RowPresentation.actionLabel(row, isPartyLeader());
|
||||
if (action != null) {
|
||||
this.font.draw(poseStack, action.getString(), actionX, rowY, RowPresentation.actionColor(row.state));
|
||||
}
|
||||
|
||||
if (hovered) {
|
||||
if (starHovered) {
|
||||
renderTooltip(poseStack, RowPresentation.favoriteTooltip(row.favorite), mouseX, mouseY);
|
||||
} else if (hovered) {
|
||||
Component hint = RowPresentation.tooltip(row, isPartyLeader());
|
||||
if (hint != null) {
|
||||
renderTooltip(poseStack, hint, mouseX, mouseY);
|
||||
@@ -547,7 +585,8 @@ public class PartyScreen extends Screen {
|
||||
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||
int rightPanelX = leftPanelX + panelWidth + PANEL_PADDING;
|
||||
int listTop = PANEL_TOP + 48;
|
||||
int listHeight = VISIBLE_ONLINE_ROWS * ONLINE_ROW_HEIGHT + 6;
|
||||
int panelBottom = PANEL_TOP + PANEL_HEIGHT;
|
||||
int listHeight = Math.min(VISIBLE_ONLINE_ROWS * ONLINE_ROW_HEIGHT + 6, Math.max(0, panelBottom - listTop - 8));
|
||||
return mouseX >= rightPanelX + 8 && mouseX <= rightPanelX + panelWidth - 8 && mouseY >= listTop && mouseY <= listTop + listHeight;
|
||||
}
|
||||
|
||||
@@ -572,6 +611,12 @@ public class PartyScreen extends Screen {
|
||||
int actionY = listTop + (index - this.onlineScrollOffset) * ONLINE_ROW_HEIGHT + 4;
|
||||
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||
int actionX = panelX + panelWidth - 110;
|
||||
int starX = panelX + 12;
|
||||
if (isFavoriteToggleHovered(mouseX, mouseY, starX, actionY)) {
|
||||
ClientFavoritePlayers.toggleFavorite(player.id);
|
||||
pushToast(new TranslatableComponent(row.favorite ? "screen.vaultpartyui.toast_favorite_removed" : "screen.vaultpartyui.toast_favorite_added", player.name), 0xE3C38C);
|
||||
return true;
|
||||
}
|
||||
if (mouseX >= actionX && mouseX <= actionX + 104 && mouseY >= actionY && mouseY <= actionY + ONLINE_ROW_HEIGHT - 2) {
|
||||
performPrimaryRowAction(row);
|
||||
return true;
|
||||
@@ -585,6 +630,49 @@ public class PartyScreen extends Screen {
|
||||
return PartyRosterService.isPartyLeader(this.currentParty, getLocalPlayerId());
|
||||
}
|
||||
|
||||
private boolean hasInviteableFavorites() {
|
||||
List<OnlineRow> rows = PartyRosterService.buildRows(
|
||||
this.onlinePlayers,
|
||||
FilterMode.ALL,
|
||||
this.currentParty,
|
||||
getLocalPlayerId(),
|
||||
this.inviteCooldownUntilMs
|
||||
);
|
||||
|
||||
for (OnlineRow row : rows) {
|
||||
if (row.favorite && row.state == RowState.INVITEABLE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void inviteFavoritePlayers() {
|
||||
List<OnlineRow> rows = PartyRosterService.buildRows(
|
||||
this.onlinePlayers,
|
||||
FilterMode.ALL,
|
||||
this.currentParty,
|
||||
getLocalPlayerId(),
|
||||
this.inviteCooldownUntilMs
|
||||
);
|
||||
|
||||
int invited = 0;
|
||||
for (OnlineRow row : rows) {
|
||||
if (!row.favorite || row.state != RowState.INVITEABLE) {
|
||||
continue;
|
||||
}
|
||||
sendPartyCommand("party invite " + row.player.name);
|
||||
this.inviteCooldownUntilMs.put(row.player.id, System.currentTimeMillis() + INVITE_COOLDOWN_MS);
|
||||
invited++;
|
||||
}
|
||||
|
||||
if (invited > 0) {
|
||||
pushToast(new TranslatableComponent("screen.vaultpartyui.toast_invited_favorites"), 0xA0E0A0);
|
||||
} else {
|
||||
pushToast(new TranslatableComponent("screen.vaultpartyui.toast_no_favorite_invites"), 0xB0B0B0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLocalPlayerInParty() {
|
||||
return PartyRosterService.isLocalPlayerInParty(this.currentParty, getLocalPlayerId());
|
||||
}
|
||||
@@ -678,16 +766,16 @@ public class PartyScreen extends Screen {
|
||||
|
||||
private String resolvePlayerName(UUID playerId) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
if (minecraft == null || playerId == null) return "?";
|
||||
if (minecraft == null || playerId == null) return "offline";
|
||||
ClientPacketListener connection = minecraft.getConnection();
|
||||
if (connection == null) return "?";
|
||||
if (connection == null) return "offline";
|
||||
for (PlayerInfo info : connection.getOnlinePlayers()) {
|
||||
GameProfile profile = info.getProfile();
|
||||
if (profile != null && playerId.equals(profile.getId())) {
|
||||
return profile.getName();
|
||||
}
|
||||
}
|
||||
return "?";
|
||||
return "offline";
|
||||
}
|
||||
|
||||
private String formatHealth(float hp) {
|
||||
@@ -717,6 +805,10 @@ public class PartyScreen extends Screen {
|
||||
return DefaultPlayerSkin.getDefaultSkin(safeId);
|
||||
}
|
||||
|
||||
private boolean isFavoriteToggleHovered(double mouseX, double mouseY, int starX, int rowY) {
|
||||
return mouseX >= starX && mouseX <= starX + STAR_SIZE && mouseY >= rowY - 1 && mouseY <= rowY + STAR_SIZE;
|
||||
}
|
||||
|
||||
private int statusColor(PartyMember.Status status) {
|
||||
if (status == null) return 0xFFFFFF;
|
||||
String s = status.name();
|
||||
@@ -744,18 +836,9 @@ public class PartyScreen extends Screen {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Fallbacks
|
||||
try {
|
||||
String os = System.getProperty("os.name").toLowerCase(java.util.Locale.ROOT);
|
||||
if (os.contains("win")) {
|
||||
Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", url});
|
||||
} else if (os.contains("mac")) {
|
||||
Runtime.getRuntime().exec(new String[]{"open", url});
|
||||
} else {
|
||||
Runtime.getRuntime().exec(new String[]{"xdg-open", url});
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// Do not attempt Runtime.exec fallbacks — these use Runtime.getRuntime() and
|
||||
// are rejected by some moderation systems (e.g. CurseForge). If the AWT
|
||||
// Desktop API is not available, silently fail to avoid using Runtime.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,4 +69,8 @@ final class RowPresentation {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Component favoriteTooltip(boolean favorite) {
|
||||
return new TranslatableComponent(favorite ? "screen.vaultpartyui.tip_unfavorite" : "screen.vaultpartyui.tip_favorite");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"screen.vaultpartyui.invite_selected": "Invite Selected",
|
||||
"screen.vaultpartyui.invite_nearby": "Invite Nearby",
|
||||
"screen.vaultpartyui.invite_all": "Invite All",
|
||||
"screen.vaultpartyui.invite_favorites": "Invite Favorites",
|
||||
"screen.vaultpartyui.remove_selected": "Remove Selected",
|
||||
"screen.vaultpartyui.accept_invite": "Accept Invite",
|
||||
"screen.vaultpartyui.decline_invite": "Decline Invite",
|
||||
@@ -22,6 +23,10 @@
|
||||
"screen.vaultpartyui.auto_accepted": "Auto-accepted invite from %s",
|
||||
"screen.vaultpartyui.toast_auto_accept_on": "Auto-accept enabled",
|
||||
"screen.vaultpartyui.toast_auto_accept_off": "Auto-accept disabled",
|
||||
"screen.vaultpartyui.toast_invited_favorites": "Invited favorite players",
|
||||
"screen.vaultpartyui.toast_no_favorite_invites": "No favorite players are available to invite",
|
||||
"screen.vaultpartyui.toast_favorite_added": "Added %s to favorites",
|
||||
"screen.vaultpartyui.toast_favorite_removed": "Removed %s from favorites",
|
||||
"screen.vaultpartyui.no_party": "You are not in a party.",
|
||||
"screen.vaultpartyui.no_target": "Type or select a player name first.",
|
||||
"screen.vaultpartyui.pending_invite": "Incoming invite from %s",
|
||||
@@ -37,6 +42,8 @@
|
||||
"screen.vaultpartyui.tip_invite": "Invite this player",
|
||||
"screen.vaultpartyui.tip_remove": "Remove this member",
|
||||
"screen.vaultpartyui.tip_member": "Only leader can remove members",
|
||||
"screen.vaultpartyui.tip_favorite": "Add this player to favorites",
|
||||
"screen.vaultpartyui.tip_unfavorite": "Remove this player from favorites",
|
||||
"screen.vaultpartyui.tip_other_party": "Player is already in another party",
|
||||
"screen.vaultpartyui.tip_cooldown": "Invite recently sent",
|
||||
"screen.vaultpartyui.tip_no_action": "Create or join a party to invite",
|
||||
|
||||
Reference in New Issue
Block a user