PvP nameplates: correct GC icons (62601/62602/62603), configurable GC IDs, Role/Job icon for enemy players

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-01-31 22:40:28 -05:00
parent 11b4c268f0
commit 80f45f5a31
7 changed files with 275 additions and 42 deletions
+113 -38
View File
@@ -145,7 +145,7 @@ namespace HSUI.Interface.Party
return false;
}
/// <summary>Gets the alliance letter (A/B/C) for an internal group index. Uses member EntityId matching to CrossRealm when GroupManager/CrossRealm use different orderings.</summary>
/// <summary>Gets the alliance letter (A/B/C) for an internal group index. GroupManager ordering can differ from game display; use LocalPlayerGroupIndex to map when CrossRealm member data is unavailable.</summary>
public bool TryGetAllianceLetter(int allianceIndex, out string letter)
{
letter = "";
@@ -154,53 +154,72 @@ namespace HSUI.Interface.Party
var info = InfoProxyCrossRealm.Instance();
if (info != null && info->GroupCount >= 3)
{
// Get first member EntityId from our group (works for both GroupManager and CrossRealm data)
uint matchEntityId = 0;
if (_allianceMembers[allianceIndex].Count > 0)
matchEntityId = _allianceMembers[allianceIndex][0].ObjectId;
// Find which CrossRealm group contains this member - they may use different ordering than GroupManager
for (int crIdx = 0; crIdx < 3 && matchEntityId != 0; crIdx++)
// Check if CrossRealm has member data - when empty, we're in-instance using GroupManager (indices differ from InfoProxy)
bool crossRealmHasData = false;
for (int i = 0; i < 3; i++)
{
var crGroup = info->CrossRealmGroups[crIdx];
for (int j = 0; j < crGroup.GroupMemberCount; j++)
if (info->CrossRealmGroups[i].GroupMemberCount > 0)
{
if (crGroup.GroupMembers[j].EntityId == matchEntityId)
crossRealmHasData = true;
break;
}
}
if (crossRealmHasData)
{
// CrossRealm populated: use EntityId lookup or direct lookup
uint matchEntityId = 0;
if (_allianceMembers[allianceIndex].Count > 0)
matchEntityId = _allianceMembers[allianceIndex][0].ObjectId;
if (matchEntityId != 0)
{
for (int crIdx = 0; crIdx < 3; crIdx++)
{
byte displayIdx = crGroup.GroupMembers[j].GroupIndex;
if (displayIdx < 3)
var crGroup = info->CrossRealmGroups[crIdx];
for (int j = 0; j < crGroup.GroupMemberCount; j++)
{
letter = ((char)('A' + displayIdx)).ToString();
return true;
if (crGroup.GroupMembers[j].EntityId == matchEntityId)
{
byte displayIdx = crGroup.GroupMembers[j].GroupIndex;
if (displayIdx < 3)
{
letter = ((char)('A' + displayIdx)).ToString();
return true;
}
}
}
}
}
var group = info->CrossRealmGroups[allianceIndex];
if (group.GroupMemberCount > 0)
{
byte displayIdx = group.GroupMembers[0].GroupIndex;
if (displayIdx < 3)
{
letter = ((char)('A' + displayIdx)).ToString();
return true;
}
}
for (byte d = 0; d < 3; d++)
{
if (InfoProxyCrossRealm.GetGroupIndex(d) == allianceIndex)
{
letter = ((char)('A' + d)).ToString();
return true;
}
}
}
// Direct lookup when CrossRealm index matches ours (e.g. both use same ordering)
var group = info->CrossRealmGroups[allianceIndex];
if (group.GroupMemberCount > 0)
{
byte displayIdx = group.GroupMembers[0].GroupIndex;
if (displayIdx < 3)
{
letter = ((char)('A' + displayIdx)).ToString();
return true;
}
}
// GetGroupIndex(displayLetter) returns internal index - find which letter maps to our index
for (byte d = 0; d < 3; d++)
{
if (InfoProxyCrossRealm.GetGroupIndex(d) == allianceIndex)
{
letter = ((char)('A' + d)).ToString();
return true;
}
}
// CrossRealm empty (in-instance with GroupManager): use direct GM-index mapping.
// GroupManager alliance index 0=A, 1=B, 2=C in the game's party list display order.
letter = ((char)('A' + allianceIndex)).ToString();
return true;
}
// Fallback when no CrossRealm data (PvP, in-instance)
// Fallback when no CrossRealm/InfoProxy data (PvP edge cases)
letter = ((char)('A' + allianceIndex)).ToString();
return true;
}
@@ -265,6 +284,7 @@ namespace HSUI.Interface.Party
{
int count = 0;
var list = new List<IPartyFramesMember>();
for (int slot = 0; slot < 8; slot++)
{
var pm = mainGroup.GetAllianceMemberByGroupAndIndex(allianceIdx, slot);
@@ -283,6 +303,31 @@ namespace HSUI.Interface.Party
list.Add(pfMember);
count++;
}
// GroupManager stores our party in _partyMembers, not GetAllianceMemberByGroupAndIndex.
// When group 2 is empty, it's our alliance—populate from GetPartyMemberByIndex.
if (count == 0 && allianceIdx == 2)
{
for (int slot = 0; slot < 8; slot++)
{
var pm = mainGroup.GetPartyMemberByIndex(slot);
if (pm == null || pm->EntityId == 0) continue;
var pfMember = new PartyFramesMember(
pm->EntityId,
count,
count,
EnmityLevel.Last,
PartyMemberStatus.None,
ReadyCheckStatus.None,
false,
false
);
pfMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false, pm->ClassJob);
list.Add(pfMember);
count++;
}
}
if (count != _lastMemberCounts[allianceIdx])
{
_allianceMembers[allianceIdx].Clear();
@@ -295,7 +340,9 @@ namespace HSUI.Interface.Party
int ourIdx = 0;
for (int slot = 0; slot < 8 && ourIdx < _allianceMembers[allianceIdx].Count; slot++)
{
var pm = mainGroup.GetAllianceMemberByGroupAndIndex(allianceIdx, slot);
PartyMember* pm = allianceIdx == 2
? mainGroup.GetPartyMemberByIndex(slot)
: mainGroup.GetAllianceMemberByGroupAndIndex(allianceIdx, slot);
if (pm == null || pm->EntityId == 0) continue;
if (_allianceMembers[allianceIdx][ourIdx] is PartyFramesMember pfMember)
pfMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false, pm->ClassJob);
@@ -372,6 +419,34 @@ namespace HSUI.Interface.Party
else
Plugin.Logger.Information($"[Alliance] Display slot {slot}: no data");
}
// Letter-resolution path debug: which code path is used and why
bool crossRealmHasData = false;
if (info != null && info->GroupCount >= 3)
{
for (int i = 0; i < 3; i++)
{
if (info->CrossRealmGroups[i].GroupMemberCount > 0)
{
crossRealmHasData = true;
break;
}
}
}
Plugin.Logger.Information($"[Alliance] crossRealmHasData={crossRealmHasData} → {(crossRealmHasData ? "EntityId/GetGroupIndex path" : "LocalPlayerGroupIndex path")}");
int dbgPlayerIdx = inst.PlayerAllianceIndex;
byte dbgLocalDisplayIdx = info != null ? info->LocalPlayerGroupIndex : (byte)255;
Plugin.Logger.Information($"[Alliance] LocalPlayerGroupIndex mapping: playerIdx={dbgPlayerIdx} localDisplayIdx={dbgLocalDisplayIdx} (0=A,1=B,2=C for our alliance)");
Plugin.Logger.Information($"[Alliance] Formula: displayIndex = (gmIndex - playerIdx + localDisplayIdx + 3) % 3");
for (int i = 0; i < 3; i++)
{
int displayIndex = (i - dbgPlayerIdx + dbgLocalDisplayIdx + 3) % 3;
char computedLetter = displayIndex >= 0 && displayIndex < 3 ? (char)('A' + displayIndex) : '?';
string ours = i == dbgPlayerIdx ? " (OURS)" : "";
Plugin.Logger.Information($"[Alliance] GM[{i}] → displayIndex=({i}-{dbgPlayerIdx}+{dbgLocalDisplayIdx}+3)%3={displayIndex} → letter '{computedLetter}'{ours}");
}
Plugin.Logger.Information("=== End Alliance Debug ===");
}