253 lines
10 KiB
C#
Executable File
253 lines
10 KiB
C#
Executable File
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Dalamud.Game;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Objects.Enums;
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
using Dalamud.Game.Text;
|
|
using Dalamud.Game.Text.SeStringHandling;
|
|
using XivCommon.Functions.NamePlates;
|
|
|
|
namespace NominaOcculta {
|
|
internal class Obscurer : IDisposable {
|
|
private Plugin Plugin { get; }
|
|
|
|
private Stopwatch UpdateTimer { get; } = new();
|
|
private IList<string> Friends { get; set; }
|
|
|
|
internal Obscurer(Plugin plugin) {
|
|
this.Plugin = plugin;
|
|
|
|
this.UpdateTimer.Start();
|
|
|
|
this.Friends = this.Plugin.Common.Functions.FriendList.List
|
|
.Select(friend => friend.Name.TextValue)
|
|
.ToList();
|
|
|
|
this.Plugin.Framework.Update += this.OnFrameworkUpdate;
|
|
this.Plugin.Functions.AtkTextNodeSetText += this.OnAtkTextNodeSetText;
|
|
this.Plugin.Common.Functions.NamePlates.OnUpdate += this.OnNamePlateUpdate;
|
|
this.Plugin.ChatGui.ChatMessage += this.OnChatMessage;
|
|
}
|
|
|
|
public void Dispose() {
|
|
this.Plugin.ChatGui.ChatMessage -= this.OnChatMessage;
|
|
this.Plugin.Common.Functions.NamePlates.OnUpdate -= this.OnNamePlateUpdate;
|
|
this.Plugin.Functions.AtkTextNodeSetText -= this.OnAtkTextNodeSetText;
|
|
this.Plugin.Framework.Update -= this.OnFrameworkUpdate;
|
|
}
|
|
|
|
private static readonly ConditionFlag[] DutyFlags = {
|
|
ConditionFlag.BoundByDuty,
|
|
ConditionFlag.BoundByDuty56,
|
|
ConditionFlag.BoundByDuty95,
|
|
ConditionFlag.BoundToDuty97,
|
|
};
|
|
|
|
private bool IsInDuty() {
|
|
return DutyFlags.Any(flag => this.Plugin.Condition[flag]);
|
|
}
|
|
|
|
private void OnFrameworkUpdate(Framework framework) {
|
|
if (this.UpdateTimer.Elapsed < TimeSpan.FromSeconds(5) || this.IsInDuty()) {
|
|
return;
|
|
}
|
|
|
|
this.Friends = this.Plugin.Common.Functions.FriendList.List
|
|
.Select(friend => friend.Name.TextValue)
|
|
.ToList();
|
|
this.UpdateTimer.Restart();
|
|
}
|
|
|
|
private void OnAtkTextNodeSetText(IntPtr node, IntPtr textPtr, ref SeString? overwrite) {
|
|
// A catch-all for UI text. This is slow, so specialised methods should be preferred.
|
|
|
|
var text = Util.ReadRawSeString(textPtr);
|
|
|
|
var changed = this.ChangeNames(text);
|
|
if (changed) {
|
|
overwrite = text;
|
|
}
|
|
}
|
|
|
|
private void OnNamePlateUpdate(NamePlateUpdateEventArgs args) {
|
|
// only replace nameplates that have objects in the table
|
|
if (!this.Plugin.Config.Enabled || !this.Plugin.NameRepository.Initialised || args.ObjectId == 0xE0000000) {
|
|
return;
|
|
}
|
|
|
|
// find the object this nameplate references
|
|
var obj = this.Plugin.ObjectTable.FirstOrDefault(o => o.ObjectId == args.ObjectId);
|
|
if (obj == null) {
|
|
return;
|
|
}
|
|
|
|
// handle owners
|
|
if (obj.OwnerId != 0xE0000000) {
|
|
if (this.Plugin.ObjectTable.FirstOrDefault(o => o.ObjectId == obj.OwnerId) is not { } owner) {
|
|
return;
|
|
}
|
|
|
|
obj = owner;
|
|
}
|
|
|
|
// only work for characters
|
|
if (obj.ObjectKind != ObjectKind.Player || obj is not Character chara) {
|
|
return;
|
|
}
|
|
|
|
var info = GetInfo(chara);
|
|
|
|
void Change(string name) {
|
|
this.ChangeName(args.Name, name, info);
|
|
this.ChangeName(args.Title, name, info);
|
|
}
|
|
|
|
var name = chara.Name.TextValue;
|
|
var playerId = this.Plugin.ClientState.LocalPlayer?.ObjectId;
|
|
var party = this.Plugin.PartyList.Select(member => member.ObjectId).ToArray();
|
|
if ((this.Plugin.Config.SelfFull || this.Plugin.Config.SelfFirst || this.Plugin.Config.SelfLast) && chara.ObjectId == playerId) {
|
|
if (this.Plugin.Config.SelfFull) {
|
|
Change(name);
|
|
}
|
|
|
|
if ((this.Plugin.Config.SelfFirst || this.Plugin.Config.SelfLast) && this.Plugin.NameRepository.GetReplacement(name, info) is { } replacement) {
|
|
var parts = name.Split(' ', 2);
|
|
var replacementParts = replacement.Split(' ', 2);
|
|
|
|
if (this.Plugin.Config.SelfFirst) {
|
|
args.Name.ReplacePlayerName(parts[0], replacementParts[0]);
|
|
args.Title.ReplacePlayerName(parts[0], replacementParts[0]);
|
|
}
|
|
|
|
if (this.Plugin.Config.SelfLast) {
|
|
args.Name.ReplacePlayerName(parts[1], replacementParts[1]);
|
|
args.Title.ReplacePlayerName(parts[1], replacementParts[1]);
|
|
}
|
|
}
|
|
} else if (this.Plugin.Config.Party && party.Contains(chara.ObjectId) && (!this.Plugin.Config.ExcludeFriends || !this.Friends.Contains(name))) {
|
|
Change(name);
|
|
} else if (this.Plugin.Config.Others && chara.ObjectId != playerId && !party.Contains(chara.ObjectId) && (!this.Plugin.Config.ExcludeFriends || !this.Friends.Contains(name))) {
|
|
Change(chara.Name.TextValue);
|
|
}
|
|
}
|
|
|
|
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
|
|
this.ChangeNames(sender);
|
|
this.ChangeNames(message);
|
|
}
|
|
|
|
private void ChangeName(SeString text, string name, (byte, byte, byte) info) {
|
|
if (this.Plugin.NameRepository.GetReplacement(name, info) is not { } replacement) {
|
|
return;
|
|
}
|
|
|
|
if (!text.ContainsPlayerName(name)) {
|
|
return;
|
|
}
|
|
|
|
text.ReplacePlayerName(name, replacement);
|
|
}
|
|
|
|
// PERFORMANCE NOTE: This potentially loops over the party list twice and the object
|
|
// table once entirely. Should be avoided if being used in a
|
|
// position where the player to replace is known.
|
|
private bool ChangeNames(SeString text) {
|
|
if (!this.Plugin.Config.Enabled || !this.Plugin.NameRepository.Initialised) {
|
|
return false;
|
|
}
|
|
|
|
var changed = false;
|
|
|
|
var player = this.Plugin.ClientState.LocalPlayer;
|
|
|
|
if (this.Plugin.Config.SelfFull) {
|
|
var playerName = player?.Name.TextValue;
|
|
if (playerName != null && text.ContainsPlayerName(playerName) && this.Plugin.NameRepository.GetReplacement(playerName, GetInfo(player!)) is { } replacement) {
|
|
text.ReplacePlayerName(playerName, replacement);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (this.Plugin.Config.SelfFirst || this.Plugin.Config.SelfLast) {
|
|
var playerName = player?.Name.TextValue;
|
|
if (playerName != null && this.Plugin.NameRepository.GetReplacement(playerName, GetInfo(player!)) is { } replacement) {
|
|
var parts = playerName.Split(' ', 2);
|
|
var replacementParts = replacement.Split(' ', 2);
|
|
|
|
if (this.Plugin.Config.SelfFirst && text.ContainsPlayerName(parts[0])) {
|
|
text.ReplacePlayerName(parts[0], replacementParts[0]);
|
|
changed = true;
|
|
}
|
|
|
|
if (this.Plugin.Config.SelfLast && text.ContainsPlayerName(parts[1])) {
|
|
text.ReplacePlayerName(parts[1], replacementParts[1]);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.Plugin.Config.Party) {
|
|
foreach (var member in this.Plugin.PartyList) {
|
|
var name = member.Name.TextValue;
|
|
|
|
var info = ((byte) 0xFF, (byte) 0xFF, member.Sex);
|
|
if (member.GameObject is Character chara) {
|
|
info = GetInfo(chara);
|
|
}
|
|
|
|
if (member.ObjectId == player?.ObjectId || !text.ContainsPlayerName(name) || this.Plugin.NameRepository.GetReplacement(name, info) is not { } replacement) {
|
|
continue;
|
|
}
|
|
|
|
if (this.Plugin.Config.ExcludeFriends && this.Friends.Contains(name)) {
|
|
continue;
|
|
}
|
|
|
|
text.ReplacePlayerName(name, replacement);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (this.Plugin.Config.Others) {
|
|
var party = this.Plugin.PartyList.Select(member => member.ObjectId).ToList();
|
|
|
|
foreach (var obj in this.Plugin.ObjectTable) {
|
|
if (obj.ObjectKind != ObjectKind.Player || obj is not Character chara || obj.ObjectId == player?.ObjectId || party.Contains(obj.ObjectId)) {
|
|
continue;
|
|
}
|
|
|
|
var name = chara.Name.TextValue;
|
|
if (this.Plugin.Config.ExcludeFriends && this.Friends.Contains(name)) {
|
|
continue;
|
|
}
|
|
|
|
var info = GetInfo(chara);
|
|
if (info.race == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (this.Plugin.NameRepository.GetReplacement(name, GetInfo(chara)) is not { } replacement) {
|
|
continue;
|
|
}
|
|
|
|
text.ReplacePlayerName(name, replacement);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
private static (byte race, byte clan, byte gender) GetInfo(Character chara) {
|
|
return (
|
|
chara.Customize[(byte) CustomizeIndex.Race],
|
|
(byte) ((chara.Customize[(byte) CustomizeIndex.Tribe] - 1) % 2),
|
|
chara.Customize[(byte) CustomizeIndex.Gender]
|
|
);
|
|
}
|
|
}
|
|
}
|