Files
HellionChat/HellionChat/Code/ChatCode.cs
T
JonKazama-Hellion 1d557f1b0e fix(code): replace GetHashCode comparison in ChatCode.Equals with field equality
Equals(object?) was delegating to GetHashCode() comparison, which is
the textbook hash-collision anti-pattern: two distinct ChatCode values
could in principle share a hash and be wrongly reported as equal. The
current GetHashCode implementation packs Type/Source/Target into 24
bits and happens to be collision-free, but the contract is fragile —
any future change to GetHashCode silently breaks Equals.

Replaced with direct field-by-field comparison of Type, Source, Target.
GetHashCode is left unchanged so dictionary/HashSet behavior stays
identical.

Pre-existing upstream issue (CodeRabbit critical finding); fixed in
v1.0.0 standalone cut where we own the codebase.
2026-05-03 21:57:17 +02:00

105 lines
3.1 KiB
C#
Executable File

using Dalamud.Game.Text;
namespace HellionChat.Code;
public class ChatCode
{
public ChatType Type { get; }
public XivChatRelationKind Source { get; }
public XivChatRelationKind Target { get; }
public ChatCode(XivChatType type, XivChatRelationKind source, XivChatRelationKind target)
{
Type = (ChatType)type;
Source = source;
Target = target;
}
public ChatCode(byte type, byte source, byte target)
: this((XivChatType)type, (XivChatRelationKind)source, (XivChatRelationKind)target) {}
public bool IsBattle()
{
switch (Type)
{
// Error isn't a battle message, but it can be just as spammy if you
// use macros with unavailable actions.
case ChatType.Error:
case ChatType.Damage:
case ChatType.Miss:
case ChatType.Action:
case ChatType.Item:
case ChatType.Healing:
case ChatType.GainBuff:
case ChatType.LoseBuff:
case ChatType.GainDebuff:
case ChatType.LoseDebuff:
case ChatType.BattleSystem:
return true;
default:
return false;
}
}
public bool IsPlayerMessage()
{
switch (Type)
{
case ChatType.Say:
case ChatType.Shout:
case ChatType.TellOutgoing:
case ChatType.TellIncoming:
case ChatType.Party:
case ChatType.CrossParty:
case ChatType.Linkshell1:
case ChatType.Linkshell2:
case ChatType.Linkshell3:
case ChatType.Linkshell4:
case ChatType.Linkshell5:
case ChatType.Linkshell6:
case ChatType.Linkshell7:
case ChatType.Linkshell8:
case ChatType.CrossLinkshell1:
case ChatType.CrossLinkshell2:
case ChatType.CrossLinkshell3:
case ChatType.CrossLinkshell4:
case ChatType.CrossLinkshell5:
case ChatType.CrossLinkshell6:
case ChatType.CrossLinkshell7:
case ChatType.CrossLinkshell8:
case ChatType.FreeCompany:
case ChatType.NoviceNetwork:
case ChatType.Yell:
case ChatType.ExtraChatLinkshell1:
case ChatType.ExtraChatLinkshell2:
case ChatType.ExtraChatLinkshell3:
case ChatType.ExtraChatLinkshell4:
case ChatType.ExtraChatLinkshell5:
case ChatType.ExtraChatLinkshell6:
case ChatType.ExtraChatLinkshell7:
case ChatType.ExtraChatLinkshell8:
return true;
default:
return false;
}
}
public int ToSortCodeV2()
{
return (byte)Type << 16 | (byte)Source << 8 | (byte)Target;
}
public override bool Equals(object? obj)
{
if (obj is not ChatCode code)
return false;
return Type == code.Type && Source == code.Source && Target == code.Target;
}
public override int GetHashCode()
{
return (byte)Type << 16 | (byte)Source << 8 | (byte)Target;
}
}