Initial commit: Vault Party UI (massuus)
This commit is contained in:
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Java
|
||||||
|
*.class
|
||||||
|
/scripts/
|
||||||
|
/local-maven/
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
|
||||||
|
# IDE/editor
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# VS Code local state
|
||||||
|
.vscode/*.log
|
||||||
|
|
||||||
|
# Forge/Minecraft run dirs
|
||||||
|
run/
|
||||||
|
logs/
|
||||||
|
crash-reports/
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Local helper artifacts
|
||||||
|
bin/
|
||||||
13
.vscode/launch.json
vendored
Normal file
13
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Attach to Forge Client",
|
||||||
|
"request": "attach",
|
||||||
|
"hostName": "localhost",
|
||||||
|
"port": 5005,
|
||||||
|
"preLaunchTask": "runClientDebug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
|
"java.configuration.updateBuildConfiguration": "automatic"
|
||||||
|
}
|
||||||
61
.vscode/tasks.json
vendored
Normal file
61
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "runClient",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "${workspaceFolder}\\gradlew.bat",
|
||||||
|
"args": ["runClient"],
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "runClientDebug",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "${workspaceFolder}\\gradlew.bat",
|
||||||
|
"args": ["runClient", "--debug-jvm"],
|
||||||
|
"group": "build",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "gradle-debug",
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "^$"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": ".*",
|
||||||
|
"endsPattern": "Listening for transport dt_socket at address: 5005"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "${workspaceFolder}\\gradlew.bat",
|
||||||
|
"args": ["build"],
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "deployToCurseForge",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "${workspaceFolder}\\scripts\\deploy.ps1",
|
||||||
|
"args": [],
|
||||||
|
"options": {
|
||||||
|
"shell": {
|
||||||
|
"executable": "powershell",
|
||||||
|
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "build-and-deploy",
|
||||||
|
"type": "shell",
|
||||||
|
"dependsOn": ["build", "deployToCurseForge"],
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
48
README.md
Normal file
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Vault Party UI
|
||||||
|
|
||||||
|
Client-side Forge mod for Vault Hunters (Minecraft 1.18.2) that provides a party management screen.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Open a party UI with a keybind (default: `I`, rebindable in Controls).
|
||||||
|
- Create/leave/disband party actions.
|
||||||
|
- Invite nearby/all actions.
|
||||||
|
- Invite handling (accept/decline) when not already in a party.
|
||||||
|
- Party member list panel.
|
||||||
|
- Online player list with per-player `Invite` / `Remove` actions.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Minecraft `1.18.2`
|
||||||
|
- Forge `40.x`
|
||||||
|
- Vault Hunters modpack (client)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Build in workspace root:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\gradlew.bat build
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile only:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\gradlew.bat compileJava
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy To CurseForge Instance
|
||||||
|
|
||||||
|
A helper script is included:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\deploy.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use VS Code task: `build-and-deploy`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- This mod is client-side UI logic.
|
||||||
|
- Server still enforces party rules and command permissions.
|
||||||
|
- For real testing, run it inside the Vault Hunters pack client instance.
|
||||||
97
build.gradle
Normal file
97
build.gradle
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
plugins {
|
||||||
|
id 'eclipse'
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'net.minecraftforge.gradle' version '5.1.+'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = mod_group_id
|
||||||
|
version = mod_version
|
||||||
|
|
||||||
|
base {
|
||||||
|
archivesName = mod_id
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||||
|
}
|
||||||
|
|
||||||
|
minecraft {
|
||||||
|
mappings channel: mapping_channel, version: mapping_version
|
||||||
|
|
||||||
|
runs {
|
||||||
|
client {
|
||||||
|
workingDirectory project.file('run')
|
||||||
|
property 'forge.logging.console.level', 'debug'
|
||||||
|
mods {
|
||||||
|
vaultpartyui {
|
||||||
|
source sourceSets.main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
workingDirectory project.file('run')
|
||||||
|
property 'forge.logging.console.level', 'debug'
|
||||||
|
mods {
|
||||||
|
vaultpartyui {
|
||||||
|
source sourceSets.main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data {
|
||||||
|
workingDirectory project.file('run')
|
||||||
|
property 'forge.logging.console.level', 'debug'
|
||||||
|
args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
|
||||||
|
mods {
|
||||||
|
vaultpartyui {
|
||||||
|
source sourceSets.main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def vaultHuntersJar = file(providers.gradleProperty('vault_hunters_jar').orElse('C:/Users/samva/curseforge/minecraft/Instances/Vault Hunters Third Edition - Remastered/mods/the_vault-1.18.2-3.21.5-remastered.6574.jar').get())
|
||||||
|
def vaultHuntersGroup = 'dev.copilot.vault'
|
||||||
|
def vaultHuntersArtifact = 'the_vault'
|
||||||
|
def vaultHuntersVersion = '1.18.2-3.21.5-remastered.6574'
|
||||||
|
def vaultHuntersRepo = file("$rootDir/local-maven")
|
||||||
|
|
||||||
|
if (!vaultHuntersJar.exists()) {
|
||||||
|
throw new GradleException("Vault Hunters jar not found at ${vaultHuntersJar}. Set -Pvault_hunters_jar to the mod jar path.")
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = 'https://maven.minecraftforge.net'
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url = uri(vaultHuntersRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
|
||||||
|
compileOnly fg.deobf("${vaultHuntersGroup}:${vaultHuntersArtifact}:${vaultHuntersVersion}")
|
||||||
|
runtimeOnly fg.deobf("${vaultHuntersGroup}:${vaultHuntersArtifact}:${vaultHuntersVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
options.release = 17
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes([
|
||||||
|
'Specification-Title' : mod_name,
|
||||||
|
'Specification-Vendor' : 'Copilot',
|
||||||
|
'Specification-Version' : '1',
|
||||||
|
'Implementation-Title' : project.name,
|
||||||
|
'Implementation-Version' : mod_version,
|
||||||
|
'Implementation-Vendor' : 'Copilot',
|
||||||
|
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
12
gradle.properties
Normal file
12
gradle.properties
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx3G
|
||||||
|
org.gradle.daemon=false
|
||||||
|
|
||||||
|
minecraft_version=1.18.2
|
||||||
|
forge_version=40.2.17
|
||||||
|
mapping_channel=official
|
||||||
|
mapping_version=1.18.2
|
||||||
|
|
||||||
|
mod_id=vaultpartyui
|
||||||
|
mod_name=Vault Party UI
|
||||||
|
mod_version=1.0.0
|
||||||
|
mod_group_id=dev.copilot.vaultpartyui
|
||||||
BIN
gradle/wrapper/gradle-cli.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-cli.jar
vendored
Normal file
Binary file not shown.
BIN
gradle/wrapper/gradle-wrapper-shared.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper-shared.jar
vendored
Normal file
Binary file not shown.
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
4
gradlew
vendored
Normal file
4
gradlew
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
APP_HOME=$(cd "$(dirname "$0")" && pwd -P)
|
||||||
|
CLASSPATH="$APP_HOME/gradle/wrapper/gradle-wrapper.jar"
|
||||||
|
exec "${JAVA_HOME:-java}" -Xmx64m -Xms64m -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
14
gradlew.bat
vendored
Normal file
14
gradlew.bat
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
SETLOCAL
|
||||||
|
SET APP_HOME=%~dp0
|
||||||
|
SET APP_BASE_NAME=%~n0
|
||||||
|
SET DIRNAME=%~dp0
|
||||||
|
IF "%DIRNAME%"=="" SET DIRNAME=.
|
||||||
|
SET DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
SET CLASSPATH=%APP_HOME%gradle\wrapper\gradle-wrapper-shared.jar;%APP_HOME%gradle\wrapper\gradle-cli.jar;%APP_HOME%gradle\wrapper\gradle-wrapper.jar
|
||||||
|
IF DEFINED JAVA_HOME (
|
||||||
|
SET JAVA_EXE=%JAVA_HOME%\bin\java.exe
|
||||||
|
) ELSE (
|
||||||
|
SET JAVA_EXE=java.exe
|
||||||
|
)
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.appname=%APP_BASE_NAME% -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>dev.copilot.vault</groupId>
|
||||||
|
<artifactId>the_vault</artifactId>
|
||||||
|
<version>1.18.2-3.21.5-remastered.6574</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
</project>
|
||||||
8
settings.gradle
Normal file
8
settings.gradle
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven { url = 'https://maven.minecraftforge.net' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = 'vault-party-ui'
|
||||||
11
src/main/java/dev/massuus/vaultpartyui/VaultPartyUiMod.java
Normal file
11
src/main/java/dev/massuus/vaultpartyui/VaultPartyUiMod.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.massuus.vaultpartyui;
|
||||||
|
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
|
||||||
|
@Mod(VaultPartyUiMod.MODID)
|
||||||
|
public class VaultPartyUiMod {
|
||||||
|
public static final String MODID = "vaultpartyui";
|
||||||
|
|
||||||
|
public VaultPartyUiMod() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.massuus.vaultpartyui.client;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
import dev.massuus.vaultpartyui.VaultPartyUiMod;
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.client.ClientRegistry;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
public final class ClientKeyMappings {
|
||||||
|
public static final KeyMapping OPEN_PARTY_UI = new KeyMapping(
|
||||||
|
"key.vaultpartyui.open_party_ui",
|
||||||
|
InputConstants.Type.KEYSYM,
|
||||||
|
GLFW.GLFW_KEY_I,
|
||||||
|
"key.categories.vaultpartyui"
|
||||||
|
);
|
||||||
|
|
||||||
|
private ClientKeyMappings() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||||
|
ClientRegistry.registerKeyBinding(OPEN_PARTY_UI);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package dev.massuus.vaultpartyui.client;
|
||||||
|
|
||||||
|
import dev.massuus.vaultpartyui.VaultPartyUiMod;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
|
||||||
|
@Mod.EventBusSubscriber(modid = VaultPartyUiMod.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||||
|
public final class ClientSetupEvents {
|
||||||
|
private ClientSetupEvents() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||||
|
ClientKeyMappings.onClientSetup(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.massuus.vaultpartyui.client;
|
||||||
|
|
||||||
|
import dev.massuus.vaultpartyui.VaultPartyUiMod;
|
||||||
|
import dev.massuus.vaultpartyui.client.screen.PartyScreen;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.client.event.InputEvent;
|
||||||
|
import net.minecraftforge.event.TickEvent;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
|
||||||
|
@Mod.EventBusSubscriber(modid = VaultPartyUiMod.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
||||||
|
public final class ClientTickEvents {
|
||||||
|
private ClientTickEvents() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||||
|
if (event.phase != TickEvent.Phase.END) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
while (ClientKeyMappings.OPEN_PARTY_UI.consumeClick()) {
|
||||||
|
if (minecraft.player != null) {
|
||||||
|
minecraft.setScreen(new PartyScreen(minecraft.screen));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,544 @@
|
|||||||
|
package dev.massuus.vaultpartyui.client.screen;
|
||||||
|
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import iskallia.vault.client.data.ClientPartyData;
|
||||||
|
import iskallia.vault.client.data.ClientPartyInviteState;
|
||||||
|
import iskallia.vault.network.message.ServerboundPartyInviteResponseMessage;
|
||||||
|
import iskallia.vault.world.data.VaultPartyData.Party;
|
||||||
|
import iskallia.vault.client.data.ClientPartyData.PartyMember;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.components.EditBox;
|
||||||
|
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||||
|
import net.minecraft.client.multiplayer.PlayerInfo;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
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 = 100;
|
||||||
|
private static final int PANEL_HEIGHT = 155;
|
||||||
|
private static final int PANEL_PADDING = 10;
|
||||||
|
private static final int ONLINE_ROW_HEIGHT = 14;
|
||||||
|
private static final int VISIBLE_ONLINE_ROWS = 8;
|
||||||
|
|
||||||
|
private final Screen parentScreen;
|
||||||
|
|
||||||
|
private Party currentParty;
|
||||||
|
private List<OnlinePlayer> onlinePlayers = Collections.emptyList();
|
||||||
|
private EditBox targetBox;
|
||||||
|
private Button createPartyButton;
|
||||||
|
private Button leavePartyButton;
|
||||||
|
private Button disbandPartyButton;
|
||||||
|
private Button inviteNearbyButton;
|
||||||
|
private Button inviteAllButton;
|
||||||
|
private Button acceptInviteButton;
|
||||||
|
private Button declineInviteButton;
|
||||||
|
private int onlineScrollOffset;
|
||||||
|
|
||||||
|
public PartyScreen(Screen parentScreen) {
|
||||||
|
super(new TranslatableComponent("screen.vaultpartyui.title"));
|
||||||
|
this.parentScreen = parentScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
super.init();
|
||||||
|
rebuildState();
|
||||||
|
|
||||||
|
int centerX = this.width / 2;
|
||||||
|
int rowWidth = BUTTON_WIDTH * 3 + BUTTON_GAP * 2;
|
||||||
|
int rowX = centerX - rowWidth / 2;
|
||||||
|
|
||||||
|
this.createPartyButton = addRenderableWidget(new Button(rowX, 24, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.create"), button -> sendPartyCommand("party create")));
|
||||||
|
this.leavePartyButton = addRenderableWidget(new Button(rowX + BUTTON_WIDTH + BUTTON_GAP, 24, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.leave"), button -> sendPartyCommand("party leave")));
|
||||||
|
this.disbandPartyButton = addRenderableWidget(new Button(rowX + (BUTTON_WIDTH + BUTTON_GAP) * 2, 24, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.disband"), button -> sendPartyCommand("party disband")));
|
||||||
|
|
||||||
|
this.inviteNearbyButton = addRenderableWidget(new Button(centerX - BUTTON_WIDTH - (BUTTON_GAP / 2), 48, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_nearby"), button -> sendPartyCommand("party invite nearby")));
|
||||||
|
this.inviteAllButton = addRenderableWidget(new Button(centerX + (BUTTON_GAP / 2), 48, BUTTON_WIDTH, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.invite_all"), button -> sendPartyCommand("party invite all")));
|
||||||
|
|
||||||
|
int inviteButtonWidth = 140;
|
||||||
|
int inviteButtonX = centerX - inviteButtonWidth - 4;
|
||||||
|
int declineButtonX = centerX + 4;
|
||||||
|
this.acceptInviteButton = addRenderableWidget(new Button(inviteButtonX, 72, inviteButtonWidth, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.accept_invite"), button -> acceptPendingInvite()));
|
||||||
|
this.declineInviteButton = addRenderableWidget(new Button(declineButtonX, 72, inviteButtonWidth, BUTTON_HEIGHT, new TranslatableComponent("screen.vaultpartyui.decline_invite"), button -> declinePendingInvite()));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
updateActionVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
super.tick();
|
||||||
|
rebuildState();
|
||||||
|
if (this.targetBox != null) {
|
||||||
|
this.targetBox.tick();
|
||||||
|
}
|
||||||
|
updateActionVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(PoseStack poseStack, int mouseX, int mouseY, float partialTick) {
|
||||||
|
this.renderBackground(poseStack);
|
||||||
|
|
||||||
|
int leftPanelX = 20;
|
||||||
|
int panelWidth = (this.width - 40 - PANEL_PADDING) / 2;
|
||||||
|
int rightPanelX = leftPanelX + panelWidth + PANEL_PADDING;
|
||||||
|
int panelBottom = PANEL_TOP + PANEL_HEIGHT;
|
||||||
|
|
||||||
|
drawPanel(poseStack, leftPanelX, PANEL_TOP, panelWidth, PANEL_HEIGHT);
|
||||||
|
drawPanel(poseStack, rightPanelX, PANEL_TOP, panelWidth, PANEL_HEIGHT);
|
||||||
|
|
||||||
|
this.drawCenteredString(poseStack, this.font, this.title, this.width / 2, 8, 0xFFFFFF);
|
||||||
|
this.drawCenteredString(poseStack, this.font, new TranslatableComponent("screen.vaultpartyui.party"), leftPanelX + panelWidth / 2, PANEL_TOP + 6, 0xE3C38C);
|
||||||
|
this.drawCenteredString(poseStack, this.font, new TranslatableComponent("screen.vaultpartyui.players"), rightPanelX + panelWidth / 2, PANEL_TOP + 6, 0xE3C38C);
|
||||||
|
|
||||||
|
if (ClientPartyInviteState.hasPendingInvite()) {
|
||||||
|
String inviterName = ClientPartyInviteState.getInviterName();
|
||||||
|
String inviteText = new TranslatableComponent("screen.vaultpartyui.pending_invite", inviterName == null ? "?" : inviterName).getString();
|
||||||
|
int noticeWidth = this.font.width(inviteText) + 14;
|
||||||
|
int noticeX = this.width / 2 - noticeWidth / 2;
|
||||||
|
fill(poseStack, noticeX, panelBottom + 8, noticeX + noticeWidth, panelBottom + 26, 0xCC1D1D1D);
|
||||||
|
fill(poseStack, noticeX, panelBottom + 8, noticeX + noticeWidth, panelBottom + 9, 0xFFE3C38C);
|
||||||
|
this.font.draw(poseStack, inviteText, noticeX + 7, panelBottom + 13, 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPartyPanel(poseStack, leftPanelX, panelWidth, mouseX, mouseY);
|
||||||
|
renderOnlinePanel(poseStack, rightPanelX, panelWidth, mouseX, mouseY);
|
||||||
|
|
||||||
|
super.render(poseStack, mouseX, mouseY, partialTick);
|
||||||
|
|
||||||
|
if (this.targetBox != null) {
|
||||||
|
this.font.draw(poseStack, new TranslatableComponent("screen.vaultpartyui.target").getString(), this.targetBox.x, this.targetBox.y - 10, 0xA0A0A0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credit
|
||||||
|
String credit = "Made by Massuus";
|
||||||
|
int creditX = this.width - this.font.width(credit) - 8;
|
||||||
|
int creditY = this.height - 18;
|
||||||
|
this.font.draw(poseStack, credit, creditX, creditY, 0xAAAAAA);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
// Check credit link click (bottom-right)
|
||||||
|
String credit = "Made by Massuus";
|
||||||
|
int creditX = this.width - this.font.width(credit) - 8;
|
||||||
|
int creditY = this.height - 18;
|
||||||
|
int creditW = this.font.width(credit);
|
||||||
|
int creditH = 10;
|
||||||
|
if (mouseX >= creditX && mouseX <= creditX + creditW && mouseY >= creditY && mouseY <= creditY + creditH) {
|
||||||
|
try {
|
||||||
|
openUrl("https://github.com/massuus/vault-hunters-party-ui");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.targetBox != null && this.targetBox.mouseClicked(mouseX, mouseY, button)) {
|
||||||
|
this.setFocused(this.targetBox);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (super.mouseClicked(mouseX, mouseY, button)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handleOnlinePlayerClick(mouseX, mouseY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseScrolled(double mouseX, double mouseY, double scrollDelta) {
|
||||||
|
if (!isInsideOnlinePanel(mouseX, mouseY)) {
|
||||||
|
return super.mouseScrolled(mouseX, mouseY, scrollDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OnlinePlayer> visiblePlayers = filteredOnlinePlayers();
|
||||||
|
int maxOffset = Math.max(0, visiblePlayers.size() - VISIBLE_ONLINE_ROWS);
|
||||||
|
if (maxOffset == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int direction = scrollDelta > 0 ? -1 : 1;
|
||||||
|
this.onlineScrollOffset = Mth.clamp(this.onlineScrollOffset + direction, 0, maxOffset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose() {
|
||||||
|
this.minecraft.setScreen(this.parentScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildState() {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft.player == null) {
|
||||||
|
this.currentParty = null;
|
||||||
|
this.onlinePlayers = Collections.emptyList();
|
||||||
|
this.onlineScrollOffset = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentParty = ClientPartyData.getParty(minecraft.player.getUUID());
|
||||||
|
this.onlinePlayers = gatherOnlinePlayers(minecraft.getConnection());
|
||||||
|
|
||||||
|
List<OnlinePlayer> visiblePlayers = filteredOnlinePlayers();
|
||||||
|
int maxOffset = Math.max(0, visiblePlayers.size() - VISIBLE_ONLINE_ROWS);
|
||||||
|
this.onlineScrollOffset = Mth.clamp(this.onlineScrollOffset, 0, maxOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<OnlinePlayer> gatherOnlinePlayers(ClientPacketListener connection) {
|
||||||
|
if (connection == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OnlinePlayer> players = new ArrayList<>();
|
||||||
|
for (PlayerInfo playerInfo : connection.getOnlinePlayers()) {
|
||||||
|
GameProfile profile = playerInfo.getProfile();
|
||||||
|
if (profile != null && profile.getId() != null && profile.getName() != null) {
|
||||||
|
players.add(new OnlinePlayer(profile.getId(), profile.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
players.sort(Comparator.comparing(player -> player.name.toLowerCase(Locale.ROOT)));
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<OnlinePlayer> filteredOnlinePlayers() {
|
||||||
|
if (this.onlinePlayers.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
String filter = this.targetBox == null ? "" : this.targetBox.getValue().trim().toLowerCase(Locale.ROOT);
|
||||||
|
if (filter.isEmpty()) {
|
||||||
|
return this.onlinePlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OnlinePlayer> filtered = new ArrayList<>();
|
||||||
|
for (OnlinePlayer player : this.onlinePlayers) {
|
||||||
|
if (player.name.toLowerCase(Locale.ROOT).contains(filter)) {
|
||||||
|
filtered.add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPartyCommand(String command) {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
ClientPacketListener connection = minecraft.getConnection();
|
||||||
|
if (connection != null) {
|
||||||
|
if (minecraft.player != null) {
|
||||||
|
minecraft.player.chat("/" + command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void acceptPendingInvite() {
|
||||||
|
if (!ClientPartyInviteState.hasPendingInvite()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID inviteId = ClientPartyInviteState.getInviteId();
|
||||||
|
if (inviteId != null) {
|
||||||
|
ServerboundPartyInviteResponseMessage.send(inviteId, true);
|
||||||
|
ClientPartyInviteState.clearInvite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void declinePendingInvite() {
|
||||||
|
if (!ClientPartyInviteState.hasPendingInvite()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID inviteId = ClientPartyInviteState.getInviteId();
|
||||||
|
if (inviteId != null) {
|
||||||
|
ServerboundPartyInviteResponseMessage.send(inviteId, false);
|
||||||
|
ClientPartyInviteState.clearInvite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInviteButtons() {
|
||||||
|
boolean hasInvite = ClientPartyInviteState.hasPendingInvite() && this.currentParty == null;
|
||||||
|
if (this.acceptInviteButton != null) {
|
||||||
|
this.acceptInviteButton.visible = hasInvite;
|
||||||
|
}
|
||||||
|
if (this.declineInviteButton != null) {
|
||||||
|
this.declineInviteButton.visible = hasInvite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateActionVisibility() {
|
||||||
|
boolean inParty = this.currentParty != null;
|
||||||
|
int centerX = this.width / 2;
|
||||||
|
|
||||||
|
if (this.createPartyButton != null) {
|
||||||
|
this.createPartyButton.visible = !inParty;
|
||||||
|
this.createPartyButton.x = centerX - (BUTTON_WIDTH / 2);
|
||||||
|
}
|
||||||
|
if (this.leavePartyButton != null) {
|
||||||
|
this.leavePartyButton.visible = inParty;
|
||||||
|
}
|
||||||
|
if (this.disbandPartyButton != null) {
|
||||||
|
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;
|
||||||
|
this.leavePartyButton.x = rowX;
|
||||||
|
this.disbandPartyButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.inviteNearbyButton != null) {
|
||||||
|
this.inviteNearbyButton.visible = inParty;
|
||||||
|
}
|
||||||
|
if (this.inviteAllButton != null) {
|
||||||
|
this.inviteAllButton.visible = inParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
this.inviteNearbyButton.x = rowX;
|
||||||
|
this.inviteAllButton.x = rowX + BUTTON_WIDTH + BUTTON_GAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInviteButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderPartyPanel(PoseStack poseStack, int panelX, int panelWidth, int mouseX, int mouseY) {
|
||||||
|
int textX = panelX + 10;
|
||||||
|
int textY = PANEL_TOP + 24;
|
||||||
|
|
||||||
|
if (this.currentParty == null) {
|
||||||
|
this.font.draw(poseStack, new TranslatableComponent("screen.vaultpartyui.no_party").getString(), textX, textY, 0xE0E0E0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID leaderId = this.currentParty.getLeader();
|
||||||
|
List<UUID> members = this.currentParty.getMembers();
|
||||||
|
this.font.draw(poseStack, new TranslatableComponent("screen.vaultpartyui.members").getString() + ": " + members.size(), textX, textY, 0xE0E0E0);
|
||||||
|
textY += 16;
|
||||||
|
|
||||||
|
for (UUID memberId : members) {
|
||||||
|
String memberName = resolvePlayerName(memberId);
|
||||||
|
PartyMember cachedMember = ClientPartyData.getCachedMember(memberId);
|
||||||
|
StringBuilder line = new StringBuilder(memberName);
|
||||||
|
if (memberId.equals(leaderId)) {
|
||||||
|
line.append(" [").append(new TranslatableComponent("screen.vaultpartyui.leader").getString()).append("]");
|
||||||
|
}
|
||||||
|
if (memberId.equals(getLocalPlayerId())) {
|
||||||
|
line.append(" [").append(new TranslatableComponent("screen.vaultpartyui.self").getString()).append("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
int color = 0xFFFFFF;
|
||||||
|
if (cachedMember != null) {
|
||||||
|
if (cachedMember.status != PartyMember.Status.NORMAL) {
|
||||||
|
line.append(" - ").append(cachedMember.status.name());
|
||||||
|
}
|
||||||
|
line.append(" - ").append(formatHealth(cachedMember.healthPts)).append(" HP");
|
||||||
|
color = statusColor(cachedMember.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.font.draw(poseStack, line.toString(), textX, textY, color);
|
||||||
|
textY += 14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderOnlinePanel(PoseStack poseStack, int panelX, int panelWidth, int mouseX, int mouseY) {
|
||||||
|
int textX = panelX + 10;
|
||||||
|
int boxWidth = panelWidth - 20;
|
||||||
|
int listTop = PANEL_TOP + 48;
|
||||||
|
int listHeight = VISIBLE_ONLINE_ROWS * ONLINE_ROW_HEIGHT + 6;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
List<OnlinePlayer> visiblePlayers = filteredOnlinePlayers();
|
||||||
|
int maxOffset = Math.max(0, visiblePlayers.size() - VISIBLE_ONLINE_ROWS);
|
||||||
|
this.onlineScrollOffset = Mth.clamp(this.onlineScrollOffset, 0, maxOffset);
|
||||||
|
|
||||||
|
int startIndex = this.onlineScrollOffset;
|
||||||
|
int endIndex = Math.min(visiblePlayers.size(), startIndex + VISIBLE_ONLINE_ROWS);
|
||||||
|
|
||||||
|
fill(poseStack, panelX + 8, listTop, panelX + panelWidth - 8, listTop + listHeight, 0x66111111);
|
||||||
|
fill(poseStack, panelX + 8, listTop, panelX + panelWidth - 8, listTop + 1, 0xFFE3C38C);
|
||||||
|
|
||||||
|
if (visiblePlayers.isEmpty()) {
|
||||||
|
this.font.draw(poseStack, "No matching players.", textX, listTop + 6, 0xA0A0A0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rowY = listTop + 4;
|
||||||
|
for (int index = startIndex; index < endIndex; index++) {
|
||||||
|
OnlinePlayer player = visiblePlayers.get(index);
|
||||||
|
boolean hovered = mouseX >= panelX + 10 && mouseX <= panelX + panelWidth - 10 && mouseY >= rowY - 2 && mouseY < rowY + ONLINE_ROW_HEIGHT - 2;
|
||||||
|
int background = hovered ? 0x663C3122 : 0x00000000;
|
||||||
|
|
||||||
|
// 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.font.draw(poseStack, player.name, panelX + 12, rowY, 0xFFFFFF);
|
||||||
|
|
||||||
|
// skip drawing actions for self
|
||||||
|
if (!player.id.equals(getLocalPlayerId())) {
|
||||||
|
int actionX = panelX + panelWidth - 84;
|
||||||
|
if (this.currentParty == null) {
|
||||||
|
// Invite button
|
||||||
|
this.font.draw(poseStack, new TranslatableComponent("screen.vaultpartyui.invite").getString(), actionX, rowY, 0xA0E0A0);
|
||||||
|
} else if (isPartyLeader()) {
|
||||||
|
// Remove button
|
||||||
|
this.font.draw(poseStack, new TranslatableComponent("screen.vaultpartyui.remove").getString(), actionX, rowY, 0xE0A0A0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rowY += ONLINE_ROW_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInsideOnlinePanel(double mouseX, double mouseY) {
|
||||||
|
int leftPanelX = 20;
|
||||||
|
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;
|
||||||
|
return mouseX >= rightPanelX + 8 && mouseX <= rightPanelX + panelWidth - 8 && mouseY >= listTop && mouseY <= listTop + listHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOnlinePlayerClick(double mouseX, double mouseY) {
|
||||||
|
if (!isInsideOnlinePanel(mouseX, mouseY)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int panelX = 20 + (this.width - 40 - PANEL_PADDING) / 2 + PANEL_PADDING;
|
||||||
|
int listTop = PANEL_TOP + 48;
|
||||||
|
int relativeY = (int)mouseY - listTop - 4;
|
||||||
|
int index = this.onlineScrollOffset + (relativeY / ONLINE_ROW_HEIGHT);
|
||||||
|
List<OnlinePlayer> visiblePlayers = filteredOnlinePlayers();
|
||||||
|
if (index < 0 || index >= visiblePlayers.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnlinePlayer player = visiblePlayers.get(index);
|
||||||
|
if (player.id.equals(getLocalPlayerId())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int actionX = panelX + ((this.width - 40 - PANEL_PADDING) / 2) - 84;
|
||||||
|
int actionY = listTop + (index - this.onlineScrollOffset) * ONLINE_ROW_HEIGHT + 4;
|
||||||
|
if (mouseX >= actionX && mouseX <= actionX + 70 && mouseY >= actionY && mouseY <= actionY + ONLINE_ROW_HEIGHT - 2) {
|
||||||
|
if (this.currentParty == null) {
|
||||||
|
sendPartyCommand("party invite " + player.name);
|
||||||
|
} else if (isPartyLeader()) {
|
||||||
|
sendPartyCommand("party remove " + player.name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPartyLeader() {
|
||||||
|
if (this.currentParty == null) return false;
|
||||||
|
UUID leader = this.currentParty.getLeader();
|
||||||
|
return leader != null && leader.equals(getLocalPlayerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID getLocalPlayerId() {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft.player == null) return null;
|
||||||
|
return minecraft.player.getUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolvePlayerName(UUID playerId) {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft == null || playerId == null) return "?";
|
||||||
|
ClientPacketListener connection = minecraft.getConnection();
|
||||||
|
if (connection == null) return "?";
|
||||||
|
for (PlayerInfo info : connection.getOnlinePlayers()) {
|
||||||
|
GameProfile profile = info.getProfile();
|
||||||
|
if (profile != null && playerId.equals(profile.getId())) {
|
||||||
|
return profile.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatHealth(float hp) {
|
||||||
|
return String.format(Locale.ROOT, "%.1f", hp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int statusColor(PartyMember.Status status) {
|
||||||
|
if (status == null) return 0xFFFFFF;
|
||||||
|
switch (status) {
|
||||||
|
case DEAD:
|
||||||
|
return 0xFF5555;
|
||||||
|
case DOWNED:
|
||||||
|
return 0xFFAA00;
|
||||||
|
default:
|
||||||
|
return 0xFFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPanel(PoseStack poseStack, int x, int y, int width, int height) {
|
||||||
|
fill(poseStack, x, y, x + width, y + height, 0xAA111111);
|
||||||
|
fill(poseStack, x, y, x + width, y + 1, 0xFFE3C38C);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openUrl(String url) {
|
||||||
|
try {
|
||||||
|
java.awt.Desktop desktop = java.awt.Desktop.isDesktopSupported() ? java.awt.Desktop.getDesktop() : null;
|
||||||
|
if (desktop != null && desktop.isSupported(java.awt.Desktop.Action.BROWSE)) {
|
||||||
|
desktop.browse(new java.net.URI(url));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class OnlinePlayer {
|
||||||
|
final UUID id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
OnlinePlayer(UUID id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/resources/META-INF/mods.toml
Normal file
27
src/main/resources/META-INF/mods.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
modLoader="javafml"
|
||||||
|
loaderVersion="[40,)"
|
||||||
|
license="All Rights Reserved"
|
||||||
|
|
||||||
|
[[mods]]
|
||||||
|
modId="vaultpartyui"
|
||||||
|
version="${file.jarVersion}"
|
||||||
|
displayName="Vault Party UI"
|
||||||
|
authors="Massuus"
|
||||||
|
description='''
|
||||||
|
Adds a client-side party management screen for Vault Hunters 1.18.2.
|
||||||
|
'''
|
||||||
|
displayTest="IGNORE_ALL_VERSION"
|
||||||
|
|
||||||
|
[[dependencies.vaultpartyui]]
|
||||||
|
modId="forge"
|
||||||
|
mandatory=true
|
||||||
|
versionRange="[40,)"
|
||||||
|
ordering="NONE"
|
||||||
|
side="CLIENT"
|
||||||
|
|
||||||
|
[[dependencies.vaultpartyui]]
|
||||||
|
modId="minecraft"
|
||||||
|
mandatory=true
|
||||||
|
versionRange="[1.18.2,1.19)"
|
||||||
|
ordering="NONE"
|
||||||
|
side="CLIENT"
|
||||||
27
src/main/resources/assets/vaultpartyui/lang/en_us.json
Normal file
27
src/main/resources/assets/vaultpartyui/lang/en_us.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"key.vaultpartyui.open_party_ui": "Open Party UI",
|
||||||
|
"key.categories.vaultpartyui": "Vault Party UI",
|
||||||
|
|
||||||
|
"screen.vaultpartyui.title": "Party Manager",
|
||||||
|
"screen.vaultpartyui.party": "Your Party",
|
||||||
|
"screen.vaultpartyui.players": "Online Players",
|
||||||
|
"screen.vaultpartyui.target": "Player Name",
|
||||||
|
"screen.vaultpartyui.search_hint": "Type a name to filter or invite",
|
||||||
|
"screen.vaultpartyui.create": "Create Party",
|
||||||
|
"screen.vaultpartyui.leave": "Leave Party",
|
||||||
|
"screen.vaultpartyui.disband": "Disband Party",
|
||||||
|
"screen.vaultpartyui.list": "List Party",
|
||||||
|
"screen.vaultpartyui.invite_selected": "Invite Selected",
|
||||||
|
"screen.vaultpartyui.invite_nearby": "Invite Nearby",
|
||||||
|
"screen.vaultpartyui.invite_all": "Invite All",
|
||||||
|
"screen.vaultpartyui.remove_selected": "Remove Selected",
|
||||||
|
"screen.vaultpartyui.accept_invite": "Accept Invite",
|
||||||
|
"screen.vaultpartyui.decline_invite": "Decline Invite",
|
||||||
|
"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",
|
||||||
|
"screen.vaultpartyui.members": "Members",
|
||||||
|
"screen.vaultpartyui.online": "Online players",
|
||||||
|
"screen.vaultpartyui.leader": "Leader",
|
||||||
|
"screen.vaultpartyui.self": "You"
|
||||||
|
}
|
||||||
6
src/main/resources/pack.mcmeta
Normal file
6
src/main/resources/pack.mcmeta
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"pack": {
|
||||||
|
"pack_format": 8,
|
||||||
|
"description": "Vault Party UI resources"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user