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
|
## 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
|
### 1.1.0
|
||||||
|
|
||||||
- Added auto-accept invites toggle that works even when the screen is closed.
|
- 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_id=vaultpartyui
|
||||||
mod_name=Vault Party UI
|
mod_name=Vault Party UI
|
||||||
mod_version=1.1.0
|
mod_version=1.2.2
|
||||||
mod_group_id=dev.massuus.vaultpartyui
|
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 class OnlineRow {
|
||||||
final OnlinePlayer player;
|
final OnlinePlayer player;
|
||||||
final RowState state;
|
final RowState state;
|
||||||
|
final boolean favorite;
|
||||||
|
|
||||||
OnlineRow(OnlinePlayer player, RowState state) {
|
OnlineRow(OnlinePlayer player, RowState state, boolean favorite) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
this.favorite = favorite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package dev.massuus.vaultpartyui.client.screen;
|
package dev.massuus.vaultpartyui.client.screen;
|
||||||
|
|
||||||
|
import dev.massuus.vaultpartyui.client.ClientFavoritePlayers;
|
||||||
import iskallia.vault.client.data.ClientPartyData;
|
import iskallia.vault.client.data.ClientPartyData;
|
||||||
import iskallia.vault.world.data.VaultPartyData.Party;
|
import iskallia.vault.world.data.VaultPartyData.Party;
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ final class PartyRosterService {
|
|||||||
if (filterMode == FilterMode.OTHER_PARTY && state != RowState.OTHER_PARTY) {
|
if (filterMode == FilterMode.OTHER_PARTY && state != RowState.OTHER_PARTY) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
rows.add(new OnlineRow(player, state));
|
rows.add(new OnlineRow(player, state, ClientFavoritePlayers.isFavorite(player.id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.sort((a, b) -> {
|
rows.sort((a, b) -> {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dev.massuus.vaultpartyui.client.screen;
|
|||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.massuus.vaultpartyui.client.ClientFavoritePlayers;
|
||||||
import dev.massuus.vaultpartyui.client.ClientPartySettings;
|
import dev.massuus.vaultpartyui.client.ClientPartySettings;
|
||||||
import iskallia.vault.client.data.ClientPartyData;
|
import iskallia.vault.client.data.ClientPartyData;
|
||||||
import iskallia.vault.client.data.ClientPartyInviteState;
|
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_WIDTH = 90;
|
||||||
private static final int BUTTON_HEIGHT = 20;
|
private static final int BUTTON_HEIGHT = 20;
|
||||||
private static final int BUTTON_GAP = 4;
|
private static final int BUTTON_GAP = 4;
|
||||||
private static final int PANEL_TOP = 124;
|
private static final int PANEL_TOP = 58;
|
||||||
private static final int PANEL_HEIGHT = 155;
|
private static final int PANEL_HEIGHT = 246;
|
||||||
private static final int PANEL_PADDING = 10;
|
private static final int PANEL_PADDING = 10;
|
||||||
private static final int ONLINE_ROW_HEIGHT = 14;
|
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 int HEAD_SIZE = 8;
|
||||||
private static final long INVITE_COOLDOWN_MS = 8000L;
|
private static final long INVITE_COOLDOWN_MS = 8000L;
|
||||||
private static final int STATE_REFRESH_INTERVAL_TICKS = 4;
|
private static final int STATE_REFRESH_INTERVAL_TICKS = 4;
|
||||||
|
private static final int STAR_SIZE = 8;
|
||||||
|
|
||||||
private final Screen parentScreen;
|
private final Screen parentScreen;
|
||||||
|
|
||||||
@@ -55,6 +57,7 @@ public class PartyScreen extends Screen {
|
|||||||
private Button disbandPartyButton;
|
private Button disbandPartyButton;
|
||||||
private Button inviteNearbyButton;
|
private Button inviteNearbyButton;
|
||||||
private Button inviteAllButton;
|
private Button inviteAllButton;
|
||||||
|
private Button inviteFavoritesButton;
|
||||||
private Button acceptInviteButton;
|
private Button acceptInviteButton;
|
||||||
private Button declineInviteButton;
|
private Button declineInviteButton;
|
||||||
private Button autoAcceptToggleButton;
|
private Button autoAcceptToggleButton;
|
||||||
@@ -74,31 +77,34 @@ public class PartyScreen extends Screen {
|
|||||||
super.init();
|
super.init();
|
||||||
rebuildState();
|
rebuildState();
|
||||||
|
|
||||||
int centerX = this.width / 2;
|
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||||
int rowWidth = BUTTON_WIDTH * 3 + BUTTON_GAP * 2;
|
int leftPanelX = 20;
|
||||||
int rowX = centerX - rowWidth / 2;
|
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")));
|
int createX = this.width / 2 - BUTTON_WIDTH / 2;
|
||||||
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.createPartyButton = addRenderableWidget(new Button(createX, 34, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.create"), button -> sendPartyCommand("party create")));
|
||||||
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")));
|
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.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(centerX + (BUTTON_GAP / 2), 62, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_all"), button -> sendPartyCommand("party invite all")));
|
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 inviteButtonWidth = 140;
|
||||||
int inviteButtonX = centerX - inviteButtonWidth - 4;
|
// Position invite accept/decline to the left/right of the centered Create Party button, same vertical level
|
||||||
int declineButtonX = centerX + 4;
|
this.acceptInviteButton = addRenderableWidget(new Button(createX - inviteButtonWidth - BUTTON_GAP, 34, inviteButtonWidth, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.accept_invite"), button -> acceptPendingInvite()));
|
||||||
this.acceptInviteButton = addRenderableWidget(new Button(inviteButtonX, 90, 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.declineInviteButton = addRenderableWidget(new Button(declineButtonX, 90, 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 -> {
|
||||||
this.autoAcceptToggleButton = addRenderableWidget(new Button(centerX - 95, 90, 190, BUTTON_HEIGHT, autoAcceptToggleLabel(), button -> {
|
|
||||||
ClientPartySettings.toggleAutoAcceptInvites();
|
ClientPartySettings.toggleAutoAcceptInvites();
|
||||||
updateAutoAcceptToggleLabel();
|
updateAutoAcceptToggleLabel();
|
||||||
pushToast(new TranslatableComponent(ClientPartySettings.isAutoAcceptInvitesEnabled() ? "screen.vaultpartyui.toast_auto_accept_on" : "screen.vaultpartyui.toast_auto_accept_off"), 0xE3C38C);
|
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 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 = new EditBox(this.font, rightPanelX + PANEL_PADDING, PANEL_TOP + 18, targetBoxWidth, 20, new TranslatableComponent("screen.vaultpartyui.target"));
|
||||||
this.targetBox.setMaxLength(64);
|
this.targetBox.setMaxLength(64);
|
||||||
addRenderableWidget(this.targetBox);
|
addRenderableWidget(this.targetBox);
|
||||||
@@ -192,7 +198,7 @@ public class PartyScreen extends Screen {
|
|||||||
int creditH = 10;
|
int creditH = 10;
|
||||||
if (mouseX >= creditX && mouseX <= creditX + creditW && mouseY >= creditY && mouseY <= creditY + creditH) {
|
if (mouseX >= creditX && mouseX <= creditX + creditW && mouseY >= creditY && mouseY <= creditY + creditH) {
|
||||||
try {
|
try {
|
||||||
openUrl("https://github.com/massuus/vault-hunters-party-ui");
|
openUrl("https://github.com/massuus/vault-party-ui");
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -391,11 +397,23 @@ public class PartyScreen extends Screen {
|
|||||||
|
|
||||||
private void updateActionVisibility() {
|
private void updateActionVisibility() {
|
||||||
boolean inParty = isLocalPlayerInParty();
|
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) {
|
if (this.createPartyButton != null) {
|
||||||
this.createPartyButton.visible = !inParty;
|
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) {
|
if (this.leavePartyButton != null) {
|
||||||
this.leavePartyButton.visible = inParty;
|
this.leavePartyButton.visible = inParty;
|
||||||
@@ -404,10 +422,9 @@ public class PartyScreen extends Screen {
|
|||||||
this.disbandPartyButton.visible = inParty;
|
this.disbandPartyButton.visible = inParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the first action row centered for the currently visible controls.
|
|
||||||
if (inParty && this.leavePartyButton != null && this.disbandPartyButton != null) {
|
if (inParty && this.leavePartyButton != null && this.disbandPartyButton != null) {
|
||||||
int rowWidth = BUTTON_WIDTH * 2 + BUTTON_GAP;
|
int rowWidth = BUTTON_WIDTH * 2 + BUTTON_GAP;
|
||||||
int rowX = centerX - rowWidth / 2;
|
int rowX = leftPanelX + panelWidth / 2 - rowWidth / 2;
|
||||||
this.leavePartyButton.x = rowX;
|
this.leavePartyButton.x = rowX;
|
||||||
this.disbandPartyButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
this.disbandPartyButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
||||||
}
|
}
|
||||||
@@ -418,16 +435,22 @@ public class PartyScreen extends Screen {
|
|||||||
if (this.inviteAllButton != null) {
|
if (this.inviteAllButton != null) {
|
||||||
this.inviteAllButton.visible = inParty;
|
this.inviteAllButton.visible = inParty;
|
||||||
}
|
}
|
||||||
|
if (this.inviteFavoritesButton != null) {
|
||||||
|
this.inviteFavoritesButton.visible = inParty;
|
||||||
|
this.inviteFavoritesButton.active = inParty && hasInviteableFavorites();
|
||||||
|
}
|
||||||
if (this.autoAcceptToggleButton != null) {
|
if (this.autoAcceptToggleButton != null) {
|
||||||
this.autoAcceptToggleButton.visible = true;
|
this.autoAcceptToggleButton.visible = true;
|
||||||
this.autoAcceptToggleButton.active = 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 && this.inviteFavoritesButton != null) {
|
||||||
if (this.inviteNearbyButton != null && this.inviteAllButton != null) {
|
int rowWidth = BUTTON_WIDTH * 3 + BUTTON_GAP * 2;
|
||||||
int rowWidth = BUTTON_WIDTH * 2 + BUTTON_GAP;
|
int rowX = rightPanelX + panelWidth / 2 - rowWidth / 2;
|
||||||
int rowX = centerX - rowWidth / 2;
|
|
||||||
this.inviteNearbyButton.x = rowX;
|
this.inviteNearbyButton.x = rowX;
|
||||||
this.inviteAllButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
this.inviteAllButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
||||||
|
this.inviteFavoritesButton.x = rowX + (BUTTON_WIDTH + BUTTON_GAP) * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateInviteButtons();
|
updateInviteButtons();
|
||||||
@@ -487,7 +510,8 @@ public class PartyScreen extends Screen {
|
|||||||
private void renderOnlinePanel(PoseStack poseStack, int panelX, int panelWidth, int mouseX, int mouseY) {
|
private void renderOnlinePanel(PoseStack poseStack, int panelX, int panelWidth, int mouseX, int mouseY) {
|
||||||
int textX = panelX + 10;
|
int textX = panelX + 10;
|
||||||
int listTop = PANEL_TOP + 48;
|
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);
|
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);
|
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)
|
// 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);
|
this.fill(poseStack, panelX + 10, rowY - 2, panelX + panelWidth - 10, rowY + ONLINE_ROW_HEIGHT - 2, background);
|
||||||
drawPlayerHead(poseStack, player.id, panelX + 12, rowY);
|
int starX = panelX + 12;
|
||||||
this.font.draw(poseStack, player.name, panelX + 12 + HEAD_SIZE + 4, rowY, RowPresentation.nameColor(row.state));
|
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 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());
|
Component action = RowPresentation.actionLabel(row, isPartyLeader());
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
this.font.draw(poseStack, action.getString(), actionX, rowY, RowPresentation.actionColor(row.state));
|
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());
|
Component hint = RowPresentation.tooltip(row, isPartyLeader());
|
||||||
if (hint != null) {
|
if (hint != null) {
|
||||||
renderTooltip(poseStack, hint, mouseX, mouseY);
|
renderTooltip(poseStack, hint, mouseX, mouseY);
|
||||||
@@ -547,7 +585,8 @@ public class PartyScreen extends Screen {
|
|||||||
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||||
int rightPanelX = leftPanelX + panelWidth + PANEL_PADDING;
|
int rightPanelX = leftPanelX + panelWidth + PANEL_PADDING;
|
||||||
int listTop = PANEL_TOP + 48;
|
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;
|
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 actionY = listTop + (index - this.onlineScrollOffset) * ONLINE_ROW_HEIGHT + 4;
|
||||||
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||||
int actionX = panelX + panelWidth - 110;
|
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) {
|
if (mouseX >= actionX && mouseX <= actionX + 104 && mouseY >= actionY && mouseY <= actionY + ONLINE_ROW_HEIGHT - 2) {
|
||||||
performPrimaryRowAction(row);
|
performPrimaryRowAction(row);
|
||||||
return true;
|
return true;
|
||||||
@@ -585,6 +630,49 @@ public class PartyScreen extends Screen {
|
|||||||
return PartyRosterService.isPartyLeader(this.currentParty, getLocalPlayerId());
|
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() {
|
private boolean isLocalPlayerInParty() {
|
||||||
return PartyRosterService.isLocalPlayerInParty(this.currentParty, getLocalPlayerId());
|
return PartyRosterService.isLocalPlayerInParty(this.currentParty, getLocalPlayerId());
|
||||||
}
|
}
|
||||||
@@ -678,16 +766,16 @@ public class PartyScreen extends Screen {
|
|||||||
|
|
||||||
private String resolvePlayerName(UUID playerId) {
|
private String resolvePlayerName(UUID playerId) {
|
||||||
Minecraft minecraft = Minecraft.getInstance();
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
if (minecraft == null || playerId == null) return "?";
|
if (minecraft == null || playerId == null) return "offline";
|
||||||
ClientPacketListener connection = minecraft.getConnection();
|
ClientPacketListener connection = minecraft.getConnection();
|
||||||
if (connection == null) return "?";
|
if (connection == null) return "offline";
|
||||||
for (PlayerInfo info : connection.getOnlinePlayers()) {
|
for (PlayerInfo info : connection.getOnlinePlayers()) {
|
||||||
GameProfile profile = info.getProfile();
|
GameProfile profile = info.getProfile();
|
||||||
if (profile != null && playerId.equals(profile.getId())) {
|
if (profile != null && playerId.equals(profile.getId())) {
|
||||||
return profile.getName();
|
return profile.getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "?";
|
return "offline";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatHealth(float hp) {
|
private String formatHealth(float hp) {
|
||||||
@@ -717,6 +805,10 @@ public class PartyScreen extends Screen {
|
|||||||
return DefaultPlayerSkin.getDefaultSkin(safeId);
|
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) {
|
private int statusColor(PartyMember.Status status) {
|
||||||
if (status == null) return 0xFFFFFF;
|
if (status == null) return 0xFFFFFF;
|
||||||
String s = status.name();
|
String s = status.name();
|
||||||
@@ -744,18 +836,9 @@ public class PartyScreen extends Screen {
|
|||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallbacks
|
// Do not attempt Runtime.exec fallbacks — these use Runtime.getRuntime() and
|
||||||
try {
|
// are rejected by some moderation systems (e.g. CurseForge). If the AWT
|
||||||
String os = System.getProperty("os.name").toLowerCase(java.util.Locale.ROOT);
|
// Desktop API is not available, silently fail to avoid using Runtime.
|
||||||
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) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,4 +69,8 @@ final class RowPresentation {
|
|||||||
return null;
|
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_selected": "Invite Selected",
|
||||||
"screen.vaultpartyui.invite_nearby": "Invite Nearby",
|
"screen.vaultpartyui.invite_nearby": "Invite Nearby",
|
||||||
"screen.vaultpartyui.invite_all": "Invite All",
|
"screen.vaultpartyui.invite_all": "Invite All",
|
||||||
|
"screen.vaultpartyui.invite_favorites": "Invite Favorites",
|
||||||
"screen.vaultpartyui.remove_selected": "Remove Selected",
|
"screen.vaultpartyui.remove_selected": "Remove Selected",
|
||||||
"screen.vaultpartyui.accept_invite": "Accept Invite",
|
"screen.vaultpartyui.accept_invite": "Accept Invite",
|
||||||
"screen.vaultpartyui.decline_invite": "Decline Invite",
|
"screen.vaultpartyui.decline_invite": "Decline Invite",
|
||||||
@@ -22,6 +23,10 @@
|
|||||||
"screen.vaultpartyui.auto_accepted": "Auto-accepted invite from %s",
|
"screen.vaultpartyui.auto_accepted": "Auto-accepted invite from %s",
|
||||||
"screen.vaultpartyui.toast_auto_accept_on": "Auto-accept enabled",
|
"screen.vaultpartyui.toast_auto_accept_on": "Auto-accept enabled",
|
||||||
"screen.vaultpartyui.toast_auto_accept_off": "Auto-accept disabled",
|
"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_party": "You are not in a party.",
|
||||||
"screen.vaultpartyui.no_target": "Type or select a player name first.",
|
"screen.vaultpartyui.no_target": "Type or select a player name first.",
|
||||||
"screen.vaultpartyui.pending_invite": "Incoming invite from %s",
|
"screen.vaultpartyui.pending_invite": "Incoming invite from %s",
|
||||||
@@ -37,6 +42,8 @@
|
|||||||
"screen.vaultpartyui.tip_invite": "Invite this player",
|
"screen.vaultpartyui.tip_invite": "Invite this player",
|
||||||
"screen.vaultpartyui.tip_remove": "Remove this member",
|
"screen.vaultpartyui.tip_remove": "Remove this member",
|
||||||
"screen.vaultpartyui.tip_member": "Only leader can remove members",
|
"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_other_party": "Player is already in another party",
|
||||||
"screen.vaultpartyui.tip_cooldown": "Invite recently sent",
|
"screen.vaultpartyui.tip_cooldown": "Invite recently sent",
|
||||||
"screen.vaultpartyui.tip_no_action": "Create or join a party to invite",
|
"screen.vaultpartyui.tip_no_action": "Create or join a party to invite",
|
||||||
|
|||||||
Reference in New Issue
Block a user