62 Commits

Author SHA1 Message Date
f66e81d6b6 v1.11.1 2024-07-20 09:28:07 +02:00
dc1d4581aa update submodule 2024-07-20 09:27:43 +02:00
a11f81ca48 Lösche CHANGELOG 2024-07-18 05:18:14 +00:00
2e76ece592 CHANGELOG hinzufügen 2024-07-18 05:17:34 +00:00
a49e29a4f1 update readme 2024-07-18 07:09:55 +02:00
63ede74217 update repo url in manifest 2024-07-18 07:02:24 +02:00
59d6be0ffd update submodule 2024-07-18 06:36:21 +02:00
f83cff5683 update submodule 2024-07-18 06:26:39 +02:00
b52174d3e3 update readme & gitmodule 2024-07-18 06:26:10 +02:00
1b23cb5da8 update submodule 2024-07-17 19:39:02 +02:00
19db0cdf41 version bump (again) 2024-07-17 15:18:12 +02:00
b570c1c003 update submodule 2024-07-17 15:16:47 +02:00
ffa9f8497a only handle player nameplates 2024-07-17 15:04:30 +02:00
b356138732 update DalamudPackager 2024-07-17 15:04:30 +02:00
361bd4ee21 update submodule 2024-07-17 15:04:30 +02:00
5fe7afe7cf update submodule 2024-07-17 15:04:30 +02:00
4fb5cc0b9d migrate chat, nameplate, etc. to new api 2024-07-17 15:04:30 +02:00
8f644ce913 update submodule 2024-07-17 15:04:30 +02:00
2bce938cf3 apply code formatting 2024-07-17 15:04:30 +02:00
b40f0d5b87 use IDatamudPluginInterface 2024-07-17 15:04:30 +02:00
681419e11b update 2024-07-17 15:04:30 +02:00
56f58e7cbd target .net 8 2024-03-23 12:46:22 +01:00
3ec790296e test 2023-12-12 09:06:31 +01:00
52f8723f84 update submodules 2023-10-07 11:25:33 +02:00
0f729a846d v0.5.2.1 2023-10-07 11:25:27 +02:00
a6111e45d9 v1.10.2 2023-10-07 11:20:26 +02:00
aa99228e96 update submodule 2023-10-07 11:20:11 +02:00
4db9f29747 v1.10.1 2023-10-06 21:01:19 +02:00
86448e1490 code formatting 2023-10-06 21:01:07 +02:00
27d900bee3 update submodules 2023-10-06 21:01:01 +02:00
746c3adf9d fix wrong version v1.10 2023-10-04 09:57:12 +02:00
e1f80a3dd5 v1.10 2023-10-04 09:40:41 +02:00
e41b875653 update submodules 2023-10-03 09:46:37 +02:00
d1a3d51e18 merge branche 'master' into master 2023-10-03 09:46:23 +02:00
db13d432ab update nuget packages 2023-10-03 09:44:52 +02:00
5072246f64 update submodule 2023-10-03 09:44:14 +02:00
30794201f0 migrate to GameConfig & cleanup warnings 2023-10-03 09:44:14 +02:00
cc1bdc7e08 update nuget packages 2023-10-03 09:44:14 +02:00
1f1b08cd59 update submodule 2023-10-03 09:44:14 +02:00
2dcf94eae2 some adjustments for API9 2023-10-03 09:44:14 +02:00
618166aac7 Merge branch 'main' of https://github.com/Pilzinsel64/PlayerTags 2023-07-19 14:34:28 +02:00
f885cd9ce3 v1.9.5 2023-07-19 14:34:20 +02:00
a8c9b4218b update translations 2023-07-19 14:33:50 +02:00
e3af61546f Update README.md 2023-07-19 14:30:59 +02:00
e8513c9988 v1.9.4 2023-05-26 20:05:06 +02:00
3bc996e356 fix /playertags command not shoing the ui 2023-05-26 20:04:34 +02:00
a49b19831a v1.9.3 2023-05-25 23:33:57 +02:00
9e655f3e0d update nuget pacckages 2023-05-25 23:32:48 +02:00
0be0a59cf7 v1.9.2 2023-05-24 11:55:41 +02:00
7f08e31bc9 update Pilz.Dalamud 2023-05-24 11:50:06 +02:00
3bddaf74b6 merge branche 'master' into master 2023-05-24 11:49:07 +02:00
2984553301 finally submoduled 2023-05-24 11:48:28 +02:00
e16b2e81cc Update .gitmodules 2023-05-24 11:40:19 +02:00
3c92859595 Update .gitmodules 2023-05-24 11:37:26 +02:00
bcb59d33eb update submodule again 2023-05-24 11:35:56 +02:00
1a4384e71f delete Pilz.Dalamud to re-add via submodule 2023-05-24 11:35:12 +02:00
fc4151bd9a Pilz.Dalamud as submodule 2023-05-24 11:34:22 +02:00
90b3694b3c change default to None for DefaultPluginDataTemplate 2023-05-23 16:07:41 +02:00
547e8d5b81 huh, what does the empty line here? 2023-05-23 16:07:19 +02:00
c47b9a39af v1.9.1 2023-05-15 07:27:27 +02:00
23d1d432d9 updated translation 2023-05-15 07:27:25 +02:00
656883ee14 add master toggle via sub-command 2023-05-15 07:27:18 +02:00
103 changed files with 5583 additions and 6829 deletions

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "Pilz.Dalamud"]
path = Pilz.Dalamud
url = https://git.pilzinsel64.de/pilz-framework/pilz.dalamud.git
branch = master

1
Pilz.Dalamud Submodule

Submodule Pilz.Dalamud added at 9900230520

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.ActivityContexts
{
public class ActivityContext
{
public ActivityType ActivityType { get; init; }
public ZoneType ZoneType { get; init; }
public ActivityContext(ActivityType activityType, ZoneType zoneType)
{
ActivityType = activityType;
ZoneType = zoneType;
}
public bool IsInDuty
{
get => ZoneType != ZoneType.Overworld;
}
}
}

View File

@@ -1,89 +0,0 @@
using Dalamud.Logging;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.ActivityContexts
{
public class ActivityContextManager : IDisposable
{
public delegate void ActivityContextChangedEventHandler(ActivityContextManager sender, ActivityContext activityContext);
public event ActivityContextChangedEventHandler ActivityContextChanged;
private readonly ExcelSheet<ContentFinderCondition> contentFinderConditionsSheet;
public ActivityContext CurrentActivityContext { get; protected set; }
public ActivityContextManager()
{
// Get condition sheet
contentFinderConditionsSheet = PluginServices.DataManager.GameData.GetExcelSheet<ContentFinderCondition>();
// Checks current territory type (if enabled/installed during a dutiy e.g.)
CheckCurrentTerritory();
// Enable event for automatic checks
PluginServices.ClientState.TerritoryChanged += ClientState_TerritoryChanged;
}
public void Dispose()
{
PluginServices.ClientState.TerritoryChanged -= ClientState_TerritoryChanged;
}
private void ClientState_TerritoryChanged(object? sender, ushort e)
{
CheckCurrentTerritory();
}
private void CheckCurrentTerritory()
{
var content = contentFinderConditionsSheet.FirstOrDefault(c => c.TerritoryType.Row == PluginServices.ClientState.TerritoryType);
ActivityType newActivityContext;
ZoneType newZoneType;
if (content == null)
{
// No content found, so we must be on the overworld
newActivityContext = ActivityType.None;
newZoneType = ZoneType.Overworld;
}
else
{
if (content.PvP)
{
newActivityContext = ActivityType.PvpDuty;
newZoneType = ZoneType.Pvp;
}
else
{
newActivityContext = ActivityType.PveDuty;
// Find correct member type
var memberType = content.ContentMemberType.Row;
if (content.RowId == 16 || content.RowId == 15)
memberType = 2; // Praetorium and Castrum Meridianum
else if (content.RowId == 735 || content.RowId == 778)
memberType = 127; // Bozja
// Check for ZoneType
newZoneType = memberType switch
{
2 => ZoneType.Doungen,
3 => ZoneType.Raid,
4 => ZoneType.AllianceRaid,
127 => ZoneType.Foray,
_ => ZoneType.Doungen,
};
}
}
CurrentActivityContext = new(newActivityContext, newZoneType);
ActivityContextChanged?.Invoke(this, CurrentActivityContext);
}
}
}

View File

@@ -1,18 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.ActivityContexts
{
[JsonConverter(typeof(StringEnumConverter))]
public enum ActivityType
{
None = 0x0,
PveDuty = 0x1,
PvpDuty = 0x2
}
}

View File

@@ -1,22 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.ActivityContexts
{
[Flags, JsonConverter(typeof(StringEnumConverter))]
public enum ZoneType
{
Overworld = 1,
Doungen = 2,
Raid = 4,
AllianceRaid = 8,
Foray = 16,
Pvp = 32,
Everywhere = int.MaxValue
}
}

View File

@@ -1,41 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud
{
public static class Extensions
{
/// <summary>
/// Removes a Payload from a given SeString.
/// Using <code>SeString.Payloads.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this SeString seString, Payload payload)
{
Remove(seString.Payloads, payload);
}
/// <summary>
/// Removes a Payload from a given list.
/// Using <code>List.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this List<Payload> payloads, Payload payload)
{
for (int i = 0; i < payloads.Count; i++)
{
if (ReferenceEquals(payloads[i], payload))
{
payloads.RemoveAt(i);
break;
}
}
}
}
}

View File

@@ -1,153 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud
{
public static class GameInterfaceHelper
{
public static SeString ReadSeString(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return new SeString();
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
return SeString.Parse(bytes);
}
return new SeString();
}
public static bool TryReadSeString(IntPtr ptr, out SeString? seString)
{
seString = null;
if (ptr == IntPtr.Zero)
{
return false;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
seString = SeString.Parse(bytes);
return true;
}
return false;
}
public static string? ReadString(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return null;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
return Encoding.UTF8.GetString(bytes);
}
return null;
}
public static bool TryReadString(IntPtr ptr, out string? str)
{
str = null;
if (ptr == IntPtr.Zero)
{
return false;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
str = Encoding.UTF8.GetString(bytes);
return true;
}
return false;
}
public static bool TryReadStringBytes(IntPtr ptr, out byte[]? bytes)
{
bytes = null;
if (ptr == IntPtr.Zero)
{
return false;
}
var size = 0;
while (Marshal.ReadByte(ptr, size) != 0)
{
size++;
}
bytes = new byte[size];
Marshal.Copy(ptr, bytes, 0, size);
return true;
}
public static IntPtr PluginAllocate(byte[] bytes)
{
IntPtr pointer = Marshal.AllocHGlobal(bytes.Length + 1);
Marshal.Copy(bytes, 0, pointer, bytes.Length);
Marshal.WriteByte(pointer, bytes.Length, 0);
return pointer;
}
public static IntPtr PluginAllocate(SeString seString)
{
return PluginAllocate(seString.Encode());
}
public static void PluginFree(IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
}
public static void PluginFree(ref IntPtr ptr)
{
PluginFree(ptr);
ptr = IntPtr.Zero;
}
public static byte[] NullTerminate(this byte[] bytes)
{
if (bytes.Length == 0 || bytes[bytes.Length - 1] != 0)
{
var newBytes = new byte[bytes.Length + 1];
Array.Copy(bytes, newBytes, bytes.Length);
newBytes[^1] = 0;
return newBytes;
}
return bytes;
}
public static unsafe IntPtr GameUIAllocate(ulong size)
{
return (IntPtr)IMemorySpace.GetUISpace()->Malloc(size, 0);
}
public static unsafe void GameFree(ref IntPtr ptr, ulong size)
{
if (ptr == IntPtr.Zero)
{
return;
}
IMemorySpace.Free((void*)ptr, size);
ptr = IntPtr.Zero;
}
}
}

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Icons
{
public class JobIconSet
{
private readonly int[] icons;
public float IconScale { get; init; }
public JobIconSet(int[] icons, float iconScale)
{
this.icons = icons;
IconScale = iconScale;
}
public int GetIcon(uint jobID)
{
return icons[jobID - 1];
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Icons
{
public enum JobIconSetName
{
Gold,
Framed,
Glowing,
Blue,
Red,
Purple,
Black,
Yellow,
Orange,
Green,
Grey,
Role
}
}

View File

@@ -1,128 +0,0 @@
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Icons
{
public class JobIconSets
{
private readonly Dictionary<JobIconSetName, JobIconSet> iconSets = new();
public JobIconSets()
{
Add(JobIconSetName.Gold, new[]
{
62001, 62002, 62003, 62004, 62005, 62006, 62007, 62008, 62009, 62010,
62011, 62012, 62013, 62014, 62015, 62016, 62017, 62018, 62019, 62020,
62021, 62022, 62023, 62024, 62025, 62026, 62027, 62028, 62029, 62030,
62031, 62032, 62033, 62034, 62035, 62036, 62037, 62038, 62039, 62040
}, 1);
Add(JobIconSetName.Framed, new[]
{
62101, 62102, 62103, 62104, 62105, 62106, 62107, 62108, 62109, 62110,
62111, 62112, 62113, 62114, 62115, 62116, 62117, 62118, 62119, 62120,
62121, 62122, 62123, 62124, 62125, 62126, 62127, 62128, 62129, 62130,
62131, 62132, 62133, 62134, 62135, 62136, 62137, 62138, 62139, 62140
});
Add(JobIconSetName.Glowing, new[]
{
62301, 62302, 62303, 62304, 62305, 62306, 62307, 62310, 62311, 62312,
62313, 62314, 62315, 62316, 62317, 62318, 62319, 62320, 62401, 62402,
62403, 62404, 62405, 62406, 62407, 62308, 62408, 62409, 62309, 62410,
62411, 62412, 62413, 62414, 62415, 62416, 62417, 62418, 62419, 62420
});
Add(JobIconSetName.Grey, new[]
{
91022, 91023, 91024, 91025, 91026, 91028, 91029, 91031, 91032, 91033,
91034, 91035, 91036, 91037, 91038, 91039, 91040, 91041, 91079, 91080,
91081, 91082, 91083, 91084, 91085, 91030, 91086, 91087, 91121, 91122,
91125, 91123, 91124, 91127, 91128, 91129, 91130, 91131, 91132, 91133
}, 2);
Add(JobIconSetName.Black, new[]
{
91522, 91523, 91524, 91525, 91526, 91528, 91529, 91531, 91532, 91533,
91534, 91535, 91536, 91537, 91538, 91539, 91540, 91541, 91579, 91580,
91581, 91582, 91583, 91584, 91585, 91530, 91586, 91587, 91621, 91622,
91625, 91623, 91624, 91627, 91628, 91629, 91630, 91631, 91632, 91633
}, 2);
Add(JobIconSetName.Yellow, new[]
{
92022, 92023, 92024, 92025, 92026, 92028, 92029, 92031, 92032, 92033,
92034, 92035, 92036, 92037, 92038, 92039, 92040, 92041, 92079, 92080,
92081, 92082, 92083, 92084, 92085, 92030, 92086, 92087, 92121, 92122,
92125, 92123, 92124, 92127, 92128, 92129, 92130, 92131, 92132, 92133
}, 2);
Add(JobIconSetName.Orange, new[]
{
92522, 92523, 92524, 92525, 92526, 92528, 92529, 92531, 92532, 92533,
92534, 92535, 92536, 92537, 92538, 92539, 92540, 92541, 92579, 92580,
92581, 92582, 92583, 92584, 92585, 92530, 92586, 92587, 92621, 92622,
92625, 92623, 92624, 92627, 92628, 92629, 92630, 92631, 92632, 92633
}, 2);
Add(JobIconSetName.Red, new[]
{
93022, 93023, 93024, 93025, 93026, 93028, 93029, 93031, 93032, 93033,
93034, 93035, 93036, 93037, 93038, 93039, 93040, 93041, 93079, 93080,
93081, 93082, 93083, 93084, 93085, 93030, 93086, 93087, 93121, 93122,
93125, 93123, 93124, 93127, 93128, 93129, 93130, 93131, 93132, 93133
}, 2);
Add(JobIconSetName.Purple, new[]
{
93522, 93523, 93524, 93525, 93526, 93528, 93529, 93531, 93532, 93533,
93534, 93535, 93536, 93537, 93538, 93539, 93540, 93541, 93579, 93580,
93581, 93582, 93583, 93584, 93585, 93530, 93586, 93587, 93621, 93622,
93625, 93623, 93624, 93627, 93628, 93629, 93630, 93631, 93632, 93633
}, 2);
Add(JobIconSetName.Blue, new[]
{
94022, 94023, 94024, 94025, 94026, 94028, 94029, 94031, 94032, 94033,
94034, 94035, 94036, 94037, 94038, 94039, 94040, 94041, 94079, 94080,
94081, 94082, 94083, 94084, 94085, 94030, 94086, 94087, 94121, 94122,
94125, 94123, 94124, 94127, 94128, 94129, 94130, 94131, 94132, 94133
}, 2);
Add(JobIconSetName.Green, new[]
{
94522, 94523, 94524, 94525, 94526, 94528, 94529, 94531, 94532, 94533,
94534, 94535, 94536, 94537, 94538, 94539, 94540, 94541, 94579, 94580,
94581, 94582, 94583, 94584, 94585, 94530, 94586, 94587, 94621, 94622,
94625, 94623, 94624, 94627, 94628, 94629, 94630, 94631, 94632, 94633
}, 2);
Add(JobIconSetName.Role, new[]
{
62581, 62584, 62581, 62584, 62586, 62582, 62502, 62502, 62503, 62504,
62505, 62506, 62507, 62508, 62509, 62510, 62511, 62512, 62581, 62584,
62581, 62584, 62586, 62582, 62587, 62587, 62587, 62582, 62584, 62584,
62586, 62581, 62582, 62584, 62587, 62587, 62581, 62586, 62584, 62582
});
}
private void Add(JobIconSetName id, int[] icons, float scale = 1f)
{
iconSets[id] = new JobIconSet(icons, scale);
}
public int GetJobIcon(JobIconSetName set, uint jobId)
{
return iconSets[set].GetIcon(jobId);
}
public float GetJobIconSale(JobIconSetName set)
{
return iconSets[set].IconScale;
}
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.EventArgs
{
public class AddonNamePlate_SetPlayerNameEventArgs : HookWithResultBaseEventArgs<IntPtr>
{
public IntPtr PlayerNameplateObjectPtr { get; set; }
public IntPtr TitlePtr { get; set; }
public IntPtr NamePtr { get; set; }
public IntPtr FreeCompanyPtr { get; set; }
public bool IsTitleAboveName { get; set; }
public bool IsTitleVisible { get; set; }
public int IconID { get; set; }
}
}

View File

@@ -1,42 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using Pilz.Dalamud.Nameplates.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.EventArgs
{
public class AddonNamePlate_SetPlayerNameManagedEventArgs : HookWithResultManagedBaseEventArgs<IntPtr>
{
public new AddonNamePlate_SetPlayerNameEventArgs OriginalEventArgs
{
get => base.OriginalEventArgs as AddonNamePlate_SetPlayerNameEventArgs;
set => base.OriginalEventArgs = value;
}
public SafeNameplateObject SafeNameplateObject { get; set; }
public SeString Title { get; internal set; }
public SeString Name { get; internal set; }
public SeString FreeCompany { get; internal set; }
public bool IsTitleAboveName
{
get => OriginalEventArgs.IsTitleAboveName;
set => OriginalEventArgs.IsTitleAboveName = value;
}
public bool IsTitleVisible
{
get => OriginalEventArgs.IsTitleVisible;
set => OriginalEventArgs.IsTitleVisible = value;
}
public int IconID
{
get => OriginalEventArgs.IconID;
set => OriginalEventArgs.IconID = value;
}
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.EventArgs
{
public abstract class HookBaseEventArgs
{
internal event Action CallOriginal;
public void Original()
{
CallOriginal?.Invoke();
}
}
}

View File

@@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.EventArgs
{
public abstract class HookManagedBaseEventArgs
{
public HookBaseEventArgs OriginalEventArgs { get; internal set; }
}
}

View File

@@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.EventArgs
{
public abstract class HookWithResultBaseEventArgs<TResult>
{
internal event Func<TResult> CallOriginal;
public TResult Result { get; set; }
// Call Original based on the given properties
public TResult Original()
{
return CallOriginal.Invoke();
}
}
}

View File

@@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.EventArgs
{
public abstract class HookWithResultManagedBaseEventArgs<TResult>
{
public HookWithResultBaseEventArgs<TResult> OriginalEventArgs { get; internal set; }
}
}

View File

@@ -1,43 +0,0 @@
using Dalamud.Logging;
using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Client.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Model
{
public class SafeAddonNameplate
{
private readonly DalamudPluginInterface Interface;
public IntPtr Pointer => PluginServices.GameGui.GetAddonByName("NamePlate", 1);
public SafeAddonNameplate(DalamudPluginInterface pluginInterface)
{
Interface = pluginInterface;
}
public unsafe SafeNameplateObject GetNamePlateObject(int index)
{
SafeNameplateObject result = null;
if (Pointer != IntPtr.Zero)
{
var npObjectArrayPtrPtr = Pointer + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32();
var npObjectArrayPtr = Marshal.ReadIntPtr(npObjectArrayPtrPtr);
if (npObjectArrayPtr != IntPtr.Zero)
{
var npObjectPtr = npObjectArrayPtr + Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject)) * index;
result = new(npObjectPtr, index);
}
}
return result;
}
}
}

View File

@@ -1,57 +0,0 @@
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Model
{
public class SafeNameplateInfo
{
public readonly IntPtr Pointer;
public readonly RaptureAtkModule.NamePlateInfo Data;
public SafeNameplateInfo(IntPtr pointer)
{
Pointer = pointer;
Data = Marshal.PtrToStructure<RaptureAtkModule.NamePlateInfo>(Pointer);
}
internal IntPtr NameAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.Name));
internal IntPtr FcNameAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.FcName));
internal IntPtr TitleAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.Title));
internal IntPtr DisplayTitleAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.DisplayTitle));
internal IntPtr LevelTextAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.LevelText));
public string Name => GetString(NameAddress);
public string FcName => GetString(FcNameAddress);
public string Title => GetString(TitleAddress);
public string DisplayTitle => GetString(DisplayTitleAddress);
public string LevelText => GetString(LevelTextAddress);
//public bool IsPlayerCharacter() => XivApi.IsPlayerCharacter(Data.ObjectID.ObjectID);
//public bool IsPartyMember() => XivApi.IsPartyMember(Data.ObjectID.ObjectID);
//public bool IsAllianceMember() => XivApi.IsAllianceMember(Data.ObjectID.ObjectID);
//public uint GetJobID() => GetJobId(Data.ObjectID.ObjectID);
private unsafe IntPtr GetStringPtr(string name)
{
var namePtr = Pointer + Marshal.OffsetOf(typeof(RaptureAtkModule.NamePlateInfo), name).ToInt32();
var stringPtrPtr = namePtr + Marshal.OffsetOf(typeof(Utf8String), nameof(Utf8String.StringPtr)).ToInt32();
var stringPtr = Marshal.ReadIntPtr(stringPtrPtr);
return stringPtr;
}
private string GetString(IntPtr stringPtr)
{
return Marshal.PtrToStringUTF8(stringPtr);
}
}
}

View File

@@ -1,128 +0,0 @@
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Model
{
public class SafeNameplateObject
{
public IntPtr Pointer { get; }
public AddonNamePlate.NamePlateObject Data { get; }
private int _Index;
private SafeNameplateInfo _NamePlateInfo;
public SafeNameplateObject(IntPtr pointer, int index = -1)
{
Pointer = pointer;
Data = Marshal.PtrToStructure<AddonNamePlate.NamePlateObject>(pointer);
_Index = index;
}
public int Index
{
get
{
int result = _Index;
if (_Index == -1)
{
var addon = XivApi.GetSafeAddonNamePlate();
var npObject0 = addon.GetNamePlateObject(0);
if (npObject0 == null)
result = -1; // NamePlateObject0 was null
else
{
var npObjectBase = npObject0.Pointer;
var npObjectSize = Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject));
var index = (Pointer.ToInt64() - npObjectBase.ToInt64()) / npObjectSize;
if (index < 0 || index >= 50)
result = -2; // NamePlateObject index was out of bounds
else
result = _Index = (int)index;
}
}
return result;
}
}
public SafeNameplateInfo NamePlateInfo
{
get
{
SafeNameplateInfo result = null;
if (_NamePlateInfo != null)
{
var rapturePtr = XivApi.RaptureAtkModulePtr;
if (rapturePtr != IntPtr.Zero)
{
var npInfoArrayPtr = rapturePtr + Marshal.OffsetOf(typeof(RaptureAtkModule), nameof(RaptureAtkModule.NamePlateInfoArray)).ToInt32();
var npInfoPtr = npInfoArrayPtr + Marshal.SizeOf(typeof(RaptureAtkModule.NamePlateInfo)) * Index;
result = _NamePlateInfo = new SafeNameplateInfo(npInfoPtr);
}
}
return result;
}
}
#region Getters
public unsafe IntPtr IconImageNodeAddress => Marshal.ReadIntPtr(Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.IconImageNode)).ToInt32());
public unsafe IntPtr NameNodeAddress => Marshal.ReadIntPtr(Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.NameText)).ToInt32());
public AtkImageNode IconImageNode => Marshal.PtrToStructure<AtkImageNode>(IconImageNodeAddress);
public AtkTextNode NameTextNode => Marshal.PtrToStructure<AtkTextNode>(NameNodeAddress);
#endregion
public unsafe bool IsVisible => Data.IsVisible;
public unsafe bool IsLocalPlayer => Data.IsLocalPlayer;
public bool IsPlayer => Data.NameplateKind == 0;
//public void SetIconScale(float scale, bool force = false)
//{
// if (force || IconImageNode.AtkResNode.ScaleX != scale || IconImageNode.AtkResNode.ScaleY != scale)
// {
// Instance.SetNodeScale(IconImageNodeAddress, scale, scale);
// }
//}
//public void SetNameScale(float scale, bool force = false)
//{
// if (force || NameTextNode.AtkResNode.ScaleX != scale || NameTextNode.AtkResNode.ScaleY != scale)
// {
// Instance.SetNodeScale(NameNodeAddress, scale, scale);
// }
//}
//public unsafe void SetName(IntPtr ptr)
//{
// NameTextNode.SetText("aaa");
//}
//public void SetIcon(int icon)
//{
// IconImageNode.LoadIconTexture(icon, 1);
//}
public void SetIconPosition(short x, short y)
{
var iconXAdjustPtr = Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.IconXAdjust)).ToInt32();
var iconYAdjustPtr = Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.IconYAdjust)).ToInt32();
Marshal.WriteInt16(iconXAdjustPtr, x);
Marshal.WriteInt16(iconYAdjustPtr, y);
}
}
}

View File

@@ -1,31 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Model
{
[JsonConverter(typeof(StringEnumConverter))]
public enum StatusIcons
{
Disconnecting = 061503,
InDuty = 061506,
ViewingCutscene = 061508,
Busy = 061509,
Idle = 061511,
DutyFinder = 061517,
PartyLeader = 061521,
PartyMember = 061522,
RolePlaying = 061545,
GroupPose = 061546,
NewAdventurer = 061523,
Mentor = 061540,
MentorPvE = 061542,
MentorCrafting = 061543,
MentorPvP = 061544,
Returner = 061547,
}
}

View File

@@ -1,203 +0,0 @@
using Dalamud.Hooking;
using Pilz.Dalamud.Nameplates.EventArgs;
using Dalamud.Utility.Signatures;
using ImGuiNET;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pilz.Dalamud.Nameplates.Model;
using Lumina.Excel.GeneratedSheets;
using System.Xml.Linq;
namespace Pilz.Dalamud.Nameplates
{
public class NameplateHooks : IDisposable
{
/// <summary>
/// Will be executed when the the Game wants to update the content of a nameplate with the details of the Player.
/// </summary>
public event AddonNamePlate_SetPlayerNameEventHandler AddonNamePlate_SetPlayerName;
public delegate void AddonNamePlate_SetPlayerNameEventHandler(AddonNamePlate_SetPlayerNameEventArgs eventArgs);
/// <summary>
/// Will be executed when the the Game wants to update the content of a nameplate with the details of the Player.
/// This will event acts on a higher level with SeString instead of IntPtr e.g.
/// </summary>
public event AddonNamePlate_SetPlayerNameManagedEventHandler AddonNamePlate_SetPlayerNameManaged;
public delegate void AddonNamePlate_SetPlayerNameManagedEventHandler(AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs);
[Signature("48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 44 0F B6 E2", DetourName = nameof(SetPlayerNameplateDetour))]
private readonly Hook<AddonNamePlate_SetPlayerNameplateDetour>? hook_AddonNamePlate_SetPlayerNameplateDetour = null;
private unsafe delegate IntPtr AddonNamePlate_SetPlayerNameplateDetour(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId);
/// <summary>
/// Defines if all hooks are enabled. If this is false, then there might be something wrong or the class already has been disposed.
/// </summary>
public bool IsValid
{
get
{
var isValid = true;
isValid &= IsHookEnabled(hook_AddonNamePlate_SetPlayerNameplateDetour);
return isValid;
}
}
/// <summary>
/// Create a new instance of NAmeplateHooks and automatically initialize and enable all Hooks.
/// </summary>
public NameplateHooks()
{
SignatureHelper.Initialise(this);
}
~NameplateHooks()
{
Dispose();
}
public void Dispose()
{
Unhook();
}
/// <summary>
/// Initialize and enable all Hooks.
/// </summary>
internal void Initialize()
{
hook_AddonNamePlate_SetPlayerNameplateDetour?.Enable();
}
/// <summary>
/// Disable all Hooks.
/// </summary>
internal void Unhook()
{
hook_AddonNamePlate_SetPlayerNameplateDetour?.Disable();
}
private static bool IsHookEnabled<T>(Hook<T> hook) where T : Delegate
{
return hook != null && hook.IsEnabled;
}
private IntPtr SetPlayerNameplateDetour(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId)
{
var result = IntPtr.Zero;
if (IsHookEnabled(hook_AddonNamePlate_SetPlayerNameplateDetour))
{
var eventArgs = new AddonNamePlate_SetPlayerNameEventArgs
{
PlayerNameplateObjectPtr = playerNameplateObjectPtr,
TitlePtr = titlePtr,
NamePtr = namePtr,
FreeCompanyPtr = freeCompanyPtr,
IsTitleAboveName = isTitleAboveName,
IsTitleVisible = isTitleVisible,
IconID = iconId
};
void callOriginal()
{
eventArgs.Result = eventArgs.Original();
}
// Add handler for the Original call
eventArgs.CallOriginal += () =>
{
return hook_AddonNamePlate_SetPlayerNameplateDetour.Original(
eventArgs.PlayerNameplateObjectPtr,
eventArgs.IsTitleAboveName,
eventArgs.IsTitleVisible,
eventArgs.TitlePtr,
eventArgs.NamePtr,
eventArgs.FreeCompanyPtr,
eventArgs.IconID);
};
// Invoke Event
var hasDefaultHookEvent = AddonNamePlate_SetPlayerName != null;
AddonNamePlate_SetPlayerName?.Invoke(eventArgs);
if (AddonNamePlate_SetPlayerNameManaged != null)
{
var freeTitle = false;
var freeName = false;
var freeFreeCompany = false;
// Create NamePlateObject if possible
var namePlateObj = new SafeNameplateObject(playerNameplateObjectPtr);
// Create new event
var managedEventArgs = new AddonNamePlate_SetPlayerNameManagedEventArgs
{
OriginalEventArgs = eventArgs,
SafeNameplateObject = namePlateObj,
Title = GameInterfaceHelper.ReadSeString(eventArgs.TitlePtr),
Name = GameInterfaceHelper.ReadSeString(eventArgs.NamePtr),
FreeCompany = GameInterfaceHelper.ReadSeString(eventArgs.FreeCompanyPtr)
};
// Get raw string content
var titleRaw = managedEventArgs.Title.Encode();
var nameRaw = managedEventArgs.Name.Encode();
var freeCompanyRaw = managedEventArgs.FreeCompany.Encode();
// Invoke Managed Event
AddonNamePlate_SetPlayerNameManaged.Invoke(managedEventArgs);
// Get new Title string ontent
var titleNewRaw = managedEventArgs.Title.Encode();
if (!titleRaw.SequenceEqual(titleNewRaw))
{
eventArgs.TitlePtr = GameInterfaceHelper.PluginAllocate(titleNewRaw);
freeTitle = true;
}
// Get new Name string ontent
var nameNewRaw = managedEventArgs.Name.Encode();
if (!nameRaw.SequenceEqual(nameNewRaw))
{
eventArgs.NamePtr = GameInterfaceHelper.PluginAllocate(nameNewRaw);
freeName = true;
}
// Get new Free Company string ontent
var freeCompanyNewRaw = managedEventArgs.FreeCompany.Encode();
if (!freeCompanyRaw.SequenceEqual(freeCompanyNewRaw))
{
eventArgs.FreeCompanyPtr = GameInterfaceHelper.PluginAllocate(freeCompanyNewRaw);
freeFreeCompany = true;
}
// Call Original as we changed something
callOriginal();
// Free memory
if (freeTitle)
GameInterfaceHelper.PluginFree(eventArgs.TitlePtr);
if (freeName)
GameInterfaceHelper.PluginFree(eventArgs.NamePtr);
if (freeFreeCompany)
GameInterfaceHelper.PluginFree(eventArgs.FreeCompanyPtr);
}
else if(!hasDefaultHookEvent)
{
// Call original in case of nothing get called, just to get secure it will not break the game when not calling it.
callOriginal();
}
// Set result
result = eventArgs.Result;
}
return result;
}
}
}

View File

@@ -1,83 +0,0 @@
using Dalamud.Hooking;
using Pilz.Dalamud.Nameplates.EventArgs;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.UI;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
using Pilz.Dalamud.Nameplates.Model;
namespace Pilz.Dalamud.Nameplates
{
public class NameplateManager : IDisposable
{
/// <summary>
/// Provides events that you can hook to.
/// </summary>
public NameplateHooks Hooks { get; init; } = new();
/// <summary>
/// Defines if all hooks are enabled and the NameplateManager is ready to go. If this is false, then there might be something wrong or something already has been disposed.
/// </summary>
public bool IsValid => Hooks.IsValid;
/// <summary>
/// Creates a new instance of the NameplateManager.
/// </summary>
public NameplateManager()
{
Hooks.Initialize();
}
~NameplateManager()
{
Dispose();
}
public void Dispose()
{
Hooks?.Dispose();
}
public static T? GetNameplateGameObject<T>(SafeNameplateObject namePlateObject) where T : GameObject
{
return GetNameplateGameObject<T>(namePlateObject.Pointer);
}
public static T? GetNameplateGameObject<T>(IntPtr nameplateObjectPtr) where T : GameObject
{
// Get the nameplate object array
var nameplateAddonPtr = PluginServices.GameGui.GetAddonByName("NamePlate", 1);
var nameplateObjectArrayPtrPtr = nameplateAddonPtr + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32();
var nameplateObjectArrayPtr = Marshal.ReadIntPtr(nameplateObjectArrayPtrPtr);
if (nameplateObjectArrayPtr == IntPtr.Zero)
{
return null;
}
// Determine the index of the nameplate object within the nameplate object array
var namePlateObjectSize = Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject));
var namePlateObjectPtr0 = nameplateObjectArrayPtr + namePlateObjectSize * 0;
var namePlateIndex = (nameplateObjectPtr.ToInt64() - namePlateObjectPtr0.ToInt64()) / namePlateObjectSize;
if (namePlateIndex < 0 || namePlateIndex >= AddonNamePlate.NumNamePlateObjects)
{
return null;
}
// Get the nameplate info array
IntPtr nameplateInfoArrayPtr = IntPtr.Zero;
unsafe
{
var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
nameplateInfoArrayPtr = new IntPtr(&framework->GetUiModule()->GetRaptureAtkModule()->NamePlateInfoArray);
}
// Get the nameplate info for the nameplate object
var namePlateInfoPtr = new IntPtr(nameplateInfoArrayPtr.ToInt64() + Marshal.SizeOf(typeof(RaptureAtkModule.NamePlateInfo)) * namePlateIndex);
RaptureAtkModule.NamePlateInfo namePlateInfo = Marshal.PtrToStructure<RaptureAtkModule.NamePlateInfo>(namePlateInfoPtr);
// Return the object for its object id
var objectId = namePlateInfo.ObjectID.ObjectID;
return PluginServices.ObjectTable.SearchById(objectId) as T;
}
}
}

View File

@@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pilz.Dalamud.Nameplates.EventArgs;
using Pilz.Dalamud.Tools.Strings;
namespace Pilz.Dalamud.Nameplates.Tools
{
public class NameplateChanges
{
private readonly Dictionary<NameplateElements, StringChangesProps> changes = new();
public NameplateChanges()
{
changes.Add(NameplateElements.Title, new());
changes.Add(NameplateElements.Name, new());
changes.Add(NameplateElements.FreeCompany, new());
}
public NameplateChanges(AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs) : this()
{
GetProps(NameplateElements.Title).Destination = eventArgs.Title;
GetProps(NameplateElements.Name).Destination = eventArgs.Name;
GetProps(NameplateElements.FreeCompany).Destination = eventArgs.FreeCompany;
}
/// <summary>
/// Gets the properties with the changes of an element of your choice where you can add your payloads to a change and setup some options.
/// </summary>
/// <param name="element">The position of your choice.</param>
/// <returns></returns>
public StringChangesProps GetProps(NameplateElements element)
{
return changes[element];
}
/// <summary>
/// Gets the changes of an element of your choice where you can add your payloads to a change.
/// </summary>
/// <param name="element">The position of your choice.</param>
/// <returns></returns>
public StringChanges GetChanges(NameplateElements element)
{
return GetProps(element).StringChanges;
}
/// <summary>
/// Gets a change of the position of the element of your choice where you can add your payloads.
/// </summary>
/// <param name="element">The position of your choice.</param>
/// <returns></returns>
public StringChange GetChange(NameplateElements element, StringPosition position)
{
return GetChanges(element).GetChange(position);
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Tools
{
public class NameplateChangesProps
{
/// <summary>
/// All the changes to the nameplate that should be made.
/// </summary>
public NameplateChanges Changes { get; set; }
public NameplateChangesProps()
{
}
public NameplateChangesProps(NameplateChanges changes) : this()
{
Changes = changes;
}
}
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Tools
{
public enum NameplateElements
{
Name,
Title,
FreeCompany
}
}

View File

@@ -1,52 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Pilz.Dalamud.ActivityContexts;
using Pilz.Dalamud.Nameplates.Model;
using Pilz.Dalamud.Tools;
using Pilz.Dalamud.Tools.Strings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Tools
{
public static class NameplateUpdateFactory
{
public static void ApplyNameplateChanges(NameplateChangesProps props)
{
foreach (NameplateElements element in Enum.GetValues(typeof(NameplateElements)))
{
var change = props.Changes.GetProps(element);
StringUpdateFactory.ApplyStringChanges(change);
}
}
public static bool ApplyStatusIconWithPrio(ref int statusIcon, int newStatusIcon, StringChange stringChange, ActivityContext activityContext, StatusIconPriorizer priorizer, bool moveIconToNameplateIfPossible)
{
bool? isPrio = null;
var fontIcon = StatusIconFontConverter.GetBitmapFontIconFromStatusIcon((StatusIcons)statusIcon);
if (moveIconToNameplateIfPossible)
{
if (fontIcon != null)
{
// Set new font icon as string change
var iconPayload = new IconPayload(fontIcon.Value);
stringChange.Payloads.Insert(0, iconPayload);
// If we moved it, we don't need it as icon anymore, yay :D
isPrio = false;
}
}
isPrio ??= priorizer.IsPriorityIcon(statusIcon, activityContext);
if (!isPrio.Value)
statusIcon = newStatusIcon;
return isPrio.Value;
}
}
}

View File

@@ -1,67 +0,0 @@
using Lumina.Excel.GeneratedSheets;
using Pilz.Dalamud.ActivityContexts;
using Pilz.Dalamud.Nameplates.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Tools
{
public class StatusIconPriorizer
{
private static StatusIconPriorizerSettings DefaultSettings { get; } = new();
public StatusIconPriorizerSettings Settings { get; init; }
public StatusIconPriorizer() : this(DefaultSettings)
{
}
public StatusIconPriorizer(StatusIconPriorizerSettings settings)
{
Settings = settings;
}
/// <summary>
/// Check for an icon that should take priority over the job icon,
/// taking into account whether or not the player is in a duty.
/// </summary>
/// <param name="iconId">The incoming icon id that is being overwritten by the plugin.</param>
/// <param name="priorityIconId">The icon id that should be used.</param>
/// <returns>Whether a priority icon was found.</returns>
public bool IsPriorityIcon(int iconId, ActivityContext activityContext)
{
bool isPrioIcon;
if (!Settings.UsePriorizedIcons && iconId != (int)StatusIcons.Disconnecting && iconId != (int)StatusIcons.Disconnecting + 50)
isPrioIcon = false;
else
{
// Select which set of priority icons to use based on whether we're in a duty
// In the future, there can be a third list used when in combat
var priorityIcons = GetPriorityIcons(activityContext);
// Determine whether the incoming icon should take priority over the job icon
// Check the id plus 50 as that's an alternately sized version
isPrioIcon = priorityIcons.Contains(iconId) || priorityIcons.Contains(iconId + 50);
}
return isPrioIcon;
}
private IEnumerable<int> GetPriorityIcons(ActivityContext activityContext)
{
StatusIconPriorizerConditionSets set;
if (activityContext.ZoneType == ZoneType.Foray)
set = StatusIconPriorizerConditionSets.InForay;
else if (activityContext.IsInDuty)
set = StatusIconPriorizerConditionSets.InDuty;
else
set = StatusIconPriorizerConditionSets.Overworld;
return Settings.GetConditionSet(set).Select(n => (int)n);
}
}
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Tools
{
public enum StatusIconPriorizerConditionSets
{
Overworld,
InDuty,
InForay
}
}

View File

@@ -1,110 +0,0 @@
using Newtonsoft.Json;
using Pilz.Dalamud.ActivityContexts;
using Pilz.Dalamud.Nameplates.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Nameplates.Tools
{
public class StatusIconPriorizerSettings
{
[JsonProperty("IconConditionSets")]
private Dictionary<StatusIconPriorizerConditionSets, List<StatusIcons>> iconConditionSets = new();
public bool UsePriorizedIcons { get; set; } = true;
[JsonConstructor]
private StatusIconPriorizerSettings(JsonConstructorAttribute dummy)
{
}
public StatusIconPriorizerSettings() : this(false)
{
}
public StatusIconPriorizerSettings(bool fillWithDefaultSettings)
{
foreach (StatusIconPriorizerConditionSets set in Enum.GetValues(typeof(StatusIconPriorizerConditionSets)))
iconConditionSets.Add(set, new List<StatusIcons>());
if (fillWithDefaultSettings)
FillWithDefaultSettings();
}
public List<StatusIcons> GetConditionSet(StatusIconPriorizerConditionSets set)
{
return iconConditionSets[set];
}
public void ResetToEmpty()
{
foreach (var kvp in iconConditionSets)
kvp.Value.Clear();
}
public void ResetToDefault()
{
ResetToEmpty();
FillWithDefaultSettings();
}
private void FillWithDefaultSettings()
{
var setOverworld = GetConditionSet(StatusIconPriorizerConditionSets.Overworld);
setOverworld.AddRange(new[]
{
StatusIcons.Disconnecting, // Disconnecting
StatusIcons.InDuty, // In Duty
StatusIcons.ViewingCutscene, // Viewing Cutscene
StatusIcons.Busy, // Busy
StatusIcons.Idle, // Idle
StatusIcons.DutyFinder, // Duty Finder
StatusIcons.PartyLeader, // Party Leader
StatusIcons.PartyMember, // Party Member
StatusIcons.RolePlaying, // Role Playing
StatusIcons.GroupPose, // Group Pose
StatusIcons.Mentor,
StatusIcons.MentorCrafting,
StatusIcons.MentorPvE,
StatusIcons.MentorPvP,
StatusIcons.Returner,
StatusIcons.NewAdventurer,
});
var setInDuty = GetConditionSet(StatusIconPriorizerConditionSets.InDuty);
setInDuty.AddRange(new[]
{
StatusIcons.Disconnecting, // Disconnecting
StatusIcons.ViewingCutscene, // Viewing Cutscene
StatusIcons.Idle, // Idle
StatusIcons.GroupPose, // Group Pose
StatusIcons.Mentor,
StatusIcons.MentorCrafting,
StatusIcons.MentorPvE,
StatusIcons.MentorPvP,
StatusIcons.Returner,
StatusIcons.NewAdventurer,
});
var setInForay = GetConditionSet(StatusIconPriorizerConditionSets.InForay);
setInForay.AddRange(new[]
{
// This allows you to see which players don't have a party
StatusIcons.InDuty, // In Duty
StatusIcons.Disconnecting, // Disconnecting
StatusIcons.ViewingCutscene, // Viewing Cutscene
StatusIcons.Idle, // Idle
StatusIcons.GroupPose, // Group Pose
StatusIcons.Mentor,
StatusIcons.MentorCrafting,
StatusIcons.MentorPvE,
StatusIcons.MentorPvP,
StatusIcons.Returner,
StatusIcons.NewAdventurer,
});
}
}
}

View File

@@ -1,66 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>annotations</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<Platforms>x64</Platforms>
</PropertyGroup>
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<Copyright>Pilzinsel64</Copyright>
<PackageProjectUrl>https://github.com/Pilzinsel64/Pilz.Dalamud</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Pilzinsel64/Pilz.Dalamud</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>0.3.1</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ImGuiScene">
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>

View File

@@ -1,28 +0,0 @@
using Dalamud.Data;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Gui;
using Dalamud.IoC;
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud
{
public class PluginServices
{
[PluginService] public static GameGui GameGui { get; set; } = null;
[PluginService] public static DalamudPluginInterface PluginInterface { get; set; } = null;
[PluginService] public static ClientState ClientState { get; set; } = null;
[PluginService] public static DataManager DataManager { get; set; } = null;
[PluginService] public static ObjectTable ObjectTable { get; set; } = null;
public static void Initialize(DalamudPluginInterface dalamudPluginInterface)
{
dalamudPluginInterface.Create<PluginServices>();
}
}
}

View File

@@ -1,41 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using Pilz.Dalamud.Nameplates.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Tools
{
public static class StatusIconFontConverter
{
public static StatusIcons? GetStatusIconFromBitmapFontIcon(BitmapFontIcon fontIcon)
{
return fontIcon switch
{
BitmapFontIcon.NewAdventurer => StatusIcons.NewAdventurer,
BitmapFontIcon.Mentor => StatusIcons.Mentor,
BitmapFontIcon.MentorPvE => StatusIcons.MentorPvE,
BitmapFontIcon.MentorCrafting => StatusIcons.MentorCrafting,
BitmapFontIcon.MentorPvP => StatusIcons.MentorPvP,
BitmapFontIcon.Returner => StatusIcons.Returner,
_ => null
};
}
public static BitmapFontIcon? GetBitmapFontIconFromStatusIcon(StatusIcons icon)
{
return icon switch
{
StatusIcons.NewAdventurer => BitmapFontIcon.NewAdventurer,
StatusIcons.Mentor => BitmapFontIcon.Mentor,
StatusIcons.MentorPvE => BitmapFontIcon.MentorPvE,
StatusIcons.MentorCrafting => BitmapFontIcon.MentorCrafting,
StatusIcons.MentorPvP => BitmapFontIcon.MentorPvP,
StatusIcons.Returner => BitmapFontIcon.Returner,
_ => null
};
}
}
}

View File

@@ -1,24 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Tools.Strings
{
public class StringChange
{
/// <summary>
/// The payloads to use for inserting/replacing.
/// </summary>
public List<Payload> Payloads { get; init; } = new();
/// <summary>
/// Defines if only one anchor payload should be used, if using anchor payloads.
/// With this true the single anchor payload will be used in StringUpdateFactory instead of the anchor payload list.
/// Not needed to be true for the most cases.
/// </summary>
public bool ForceUsingSingleAnchorPayload { get; set; } = false;
}
}

View File

@@ -1,40 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Tools.Strings
{
public class StringChanges
{
private readonly Dictionary<StringPosition, StringChange> changes = new();
public StringChanges()
{
changes.Add(StringPosition.Before, new StringChange());
changes.Add(StringPosition.After, new StringChange());
changes.Add(StringPosition.Replace, new StringChange());
}
/// <summary>
/// Gets a change of the position of your choice where you can add your payloads.
/// </summary>
/// <param name="position">The position of your choice.</param>
/// <returns></returns>
public StringChange GetChange(StringPosition position)
{
return changes[position];
}
/// <summary>
/// Checks if there is any string change listed.
/// </summary>
/// <returns></returns>
public bool Any()
{
return changes.Sum(n => n.Value.Payloads.Count) != 0;
}
}
}

View File

@@ -1,31 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Tools.Strings
{
public class StringChangesProps
{
/// <summary>
/// The string where the changes should be applied.
/// </summary>
public SeString Destination { get; set; }
/// <summary>
/// The changes that should be applied to the destination.
/// </summary>
public StringChanges StringChanges { get; set; } = new();
/// <summary>
/// Payloads to use as anchor where the changes should be applied to.
/// </summary>
public List<Payload> AnchorPayloads { get; set; } = new();
/// <summary>
/// A single payload to use as anchor where the changes should be applied to.
/// This property will only be used if StringChange.ForceSingleAnchorPayload is true.
/// </summary>
public Payload AnchorPayload { get; set; }
}
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Tools.Strings
{
public enum StringPosition
{
Before,
After,
Replace
}
}

View File

@@ -1,132 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Lumina.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Dalamud.Tools.Strings
{
public static class StringUpdateFactory
{
public static void ApplyStringChanges(StringChangesProps props)
{
if (props.StringChanges != null && props.StringChanges.Any())
{
var seString = props.Destination;
List<StringPosition> stringPositionsOrdered = GetOrderedStringPositions(props);
foreach (var stringPosition in stringPositionsOrdered)
{
var stringChange = props.StringChanges.GetChange(stringPosition);
if (stringChange != null && stringChange.Payloads.Any())
{
AddSpacesBetweenTextPayloads(stringChange.Payloads, stringPosition);
if (stringPosition == StringPosition.Before)
{
Payload anchorFirst = stringChange.ForceUsingSingleAnchorPayload ? props.AnchorPayload : props.AnchorPayloads?.FirstOrDefault();
if (anchorFirst != null)
{
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorFirst);
seString.Payloads.InsertRange(anchorPayloadIndex, stringChange.Payloads);
}
else
seString.Payloads.InsertRange(0, stringChange.Payloads);
}
else if (stringPosition == StringPosition.After)
{
Payload anchorLast = stringChange.ForceUsingSingleAnchorPayload ? props.AnchorPayload : props.AnchorPayloads?.LastOrDefault();
if (anchorLast != null)
{
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorLast);
seString.Payloads.InsertRange(anchorPayloadIndex + 1, stringChange.Payloads);
}
else
seString.Payloads.AddRange(stringChange.Payloads);
}
else if (stringPosition == StringPosition.Replace)
{
Payload anchorReplace = props.AnchorPayload;
if (anchorReplace != null)
{
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorReplace);
seString.Payloads.InsertRange(anchorPayloadIndex, stringChange.Payloads);
seString.Remove(anchorReplace);
}
else
{
seString.Payloads.Clear();
seString.Payloads.AddRange(stringChange.Payloads);
}
}
}
}
}
}
private static void AddSpacesBetweenTextPayloads(List<Payload> payloads, StringPosition tagPosition)
{
if (payloads != null && payloads.Any())
{
var indicesToInsertSpacesAt = new List<int>();
var lastTextPayloadIndex = -1;
static TextPayload getNewTextPayload() => new(" ");
foreach (var payload in payloads.Reverse<Payload>())
{
if (payload is IconPayload iconPayload)
lastTextPayloadIndex = -1;
else if (payload is TextPayload textPayload)
{
if (lastTextPayloadIndex != -1)
indicesToInsertSpacesAt.Add(payloads.IndexOf(textPayload) + 1);
lastTextPayloadIndex = payloads.IndexOf(textPayload);
}
}
foreach (var indexToInsertSpaceAt in indicesToInsertSpacesAt)
payloads.Insert(indexToInsertSpaceAt, getNewTextPayload());
// Decide whether to add a space to the end
if (tagPosition == StringPosition.Before)
{
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
if (significantPayloads.LastOrDefault() is TextPayload)
payloads.Add(getNewTextPayload());
}
// Decide whether to add a space to the beginning
else if (tagPosition == StringPosition.After)
{
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
if (significantPayloads.FirstOrDefault() is TextPayload)
payloads.Insert(0, getNewTextPayload());
}
}
}
private static List<StringPosition> GetOrderedStringPositions(StringChangesProps props)
{
var tagPositionsOrdered = new List<StringPosition>();
// If there's no anchor payload, do replaces first so that befores and afters are based on the replaced data
if (props.AnchorPayloads == null || !props.AnchorPayloads.Any())
tagPositionsOrdered.Add(StringPosition.Replace);
tagPositionsOrdered.Add(StringPosition.Before);
tagPositionsOrdered.Add(StringPosition.After);
// If there is an anchor payload, do replaces last so that we still know which payload needs to be removed
if (props.AnchorPayloads != null && props.AnchorPayloads.Any())
tagPositionsOrdered.Add(StringPosition.Replace);
return tagPositionsOrdered;
}
}
}

View File

@@ -1,39 +0,0 @@
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pilz.Dalamud.Nameplates.Model;
namespace Pilz.Dalamud
{
public class XivApi
{
private static IntPtr _RaptureAtkModulePtr = IntPtr.Zero;
public static IntPtr RaptureAtkModulePtr
{
get
{
if (_RaptureAtkModulePtr == IntPtr.Zero)
{
unsafe
{
var framework = Framework.Instance();
var uiModule = framework->GetUiModule();
_RaptureAtkModulePtr = new IntPtr(uiModule->GetRaptureAtkModule());
}
}
return _RaptureAtkModulePtr;
}
}
public static SafeAddonNameplate GetSafeAddonNamePlate()
{
return new(PluginServices.PluginInterface);
}
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {}
}
}

View File

@@ -5,7 +5,7 @@ VisualStudioVersion = 17.3.32929.385
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlayerTags", "PlayerTags\PlayerTags.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Dalamud", "Pilz.Dalamud\Pilz.Dalamud.csproj", "{D0362D71-E77F-4739-80BE-CD4454188B8F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Dalamud", "Pilz.Dalamud\Pilz.Dalamud\Pilz.Dalamud.csproj", "{D0362D71-E77F-4739-80BE-CD4454188B8F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -26,6 +26,7 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_ShowErrorsInErrorList = False
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection
EndGlobal

View File

@@ -1,74 +1,41 @@
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Configuration.GameConfig
namespace PlayerTags.Configuration.GameConfig;
public class GameConfigHelper
{
public class GameConfigHelper
private static GameConfigHelper instance = null;
private unsafe static ConfigModule* configModule = null;
public static GameConfigHelper Instance
{
private static GameConfigHelper instance = null;
private unsafe static ConfigModule* configModule = null;
public static GameConfigHelper Instance
get
{
get
{
instance ??= new GameConfigHelper();
return instance;
}
}
private GameConfigHelper()
{
unsafe
{
configModule = ConfigModule.Instance();
}
}
private int? GetIntValue(ConfigOption option)
{
int? value = null;
unsafe
{
var index = configModule->GetIndex(option);
if (index.HasValue)
value = configModule->GetIntValue(index.Value);
}
return value;
}
public LogNameType? GetLogNameType()
{
LogNameType? logNameType = null;
int? value = GetIntValue(ConfigOption.LogNameType);
if (value.HasValue)
{
switch (value)
{
case 0:
logNameType = LogNameType.FullName;
break;
case 1:
logNameType = LogNameType.LastNameShorted;
break;
case 2:
logNameType = LogNameType.FirstNameShorted;
break;
case 3:
logNameType = LogNameType.Initials;
break;
}
}
return logNameType;
instance ??= new GameConfigHelper();
return instance;
}
}
private GameConfigHelper()
{
unsafe
{
configModule = ConfigModule.Instance();
}
}
private uint? GetIntValue(ConfigOption option)
{
if (PluginServices.GameConfig.UiConfig.TryGetUInt(nameof(ConfigOption.LogNameType), out var value))
return value;
return null;
}
public LogNameType? GetLogNameType()
{
uint? value = GetIntValue(ConfigOption.LogNameType);
if (value != null)
return (LogNameType)value;
return null;
}
}

View File

@@ -1,16 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Configuration.GameConfig;
namespace PlayerTags.Configuration.GameConfig
public enum LogNameType : uint
{
public enum LogNameType
{
FullName,
LastNameShorted,
FirstNameShorted,
Initials
}
FullName,
LastNameShorted,
FirstNameShorted,
Initials
}

View File

@@ -2,290 +2,348 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Pilz.Dalamud.ActivityContexts;
using Pilz.Dalamud.Nameplates.Tools;
using Pilz.Dalamud.Tools.NamePlates;
using PlayerTags.Data;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
namespace PlayerTags.Configuration
namespace PlayerTags.Configuration;
[Serializable]
public class PluginConfiguration : IPluginConfiguration
{
[Serializable]
public class PluginConfiguration : IPluginConfiguration
private const int DEFAULT_CONFIG_VERSION = 1;
[JsonProperty]
public int RootVersion { get; private set; } = DEFAULT_CONFIG_VERSION;
public int Version { get; set; } = DEFAULT_CONFIG_VERSION;
public bool IsVisible = false;
public bool EnabledGlobal = true;
[JsonProperty("GeneralOptionsV2")]
public Dictionary<ActivityType, GeneralOptionsClass> GeneralOptions = new()
{
private const int DEFAULT_CONFIG_VERSION = 1;
{ ActivityType.None, new GeneralOptionsClass() },
{ ActivityType.PveDuty, new GeneralOptionsClass() },
{ ActivityType.PvpDuty, new GeneralOptionsClass() }
};
[JsonProperty]
public int RootVersion { get; private set; } = DEFAULT_CONFIG_VERSION;
public int Version { get; set; } = DEFAULT_CONFIG_VERSION;
public bool IsVisible = false;
public DefaultPluginDataTemplate DefaultPluginDataTemplate = DefaultPluginDataTemplate.None;
public StatusIconPriorizerSettings StatusIconPriorizerSettings = new(true);
public bool MoveStatusIconToNameplateTextIfPossible = true;
public bool IsPlayerNameRandomlyGenerated = false;
public bool IsCustomTagsContextMenuEnabled = true;
public bool IsShowInheritedPropertiesEnabled = true;
public bool IsPlayersTabOrderedByProximity = false;
public bool IsPlayersTabSelfVisible = true;
public bool IsPlayersTabFriendsVisible = true;
public bool IsPlayersTabPartyVisible = true;
public bool IsPlayersTabAllianceVisible = true;
public bool IsPlayersTabEnemiesVisible = true;
public bool IsPlayersTabOthersVisible = false;
public bool IsGeneralOptionsAllTheSameEnabled = true;
[JsonProperty("GeneralOptionsV2")]
public Dictionary<ActivityType, GeneralOptionsClass> GeneralOptions = new()
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<Role, Dictionary<string, InheritableData>> RoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<DpsRole, Dictionary<string, InheritableData>> DpsRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<RangedDpsRole, Dictionary<string, InheritableData>> RangedDpsRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<LandHandRole, Dictionary<string, InheritableData>> LandHandRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, Dictionary<string, InheritableData>> JobTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllCustomTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Dictionary<string, InheritableData>> CustomTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Identity> Identities = [];
#region Obsulate Properties
[Obsolete]
[JsonProperty("GeneralOptions")]
private Dictionary<Data.ActivityContext, GeneralOptionsClass> GeneralOptionsV1
{
set
{
{ ActivityType.None, new GeneralOptionsClass() },
{ ActivityType.PveDuty, new GeneralOptionsClass() },
{ ActivityType.PvpDuty, new GeneralOptionsClass() }
GeneralOptions.Clear();
foreach (var kvp in value)
GeneralOptions.Add((ActivityType)kvp.Key, kvp.Value);
}
}
[JsonProperty("NameplateFreeCompanyVisibility"), Obsolete]
private NameplateFreeCompanyVisibility NameplateFreeCompanyVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateFreeCompanyVisibility = value;
}
}
[JsonProperty("NameplateTitleVisibility"), Obsolete]
public NameplateTitleVisibility NameplateTitleVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitleVisibility = value;
}
}
[JsonProperty("NameplateTitlePosition"), Obsolete]
public NameplateTitlePosition NameplateTitlePositionV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitlePosition = value;
}
}
[JsonProperty("IsApplyTagsToAllChatMessagesEnabled"), Obsolete]
private bool IsApplyTagsToAllChatMessagesEnabledV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].IsApplyTagsToAllChatMessagesEnabled = value;
}
}
#endregion
public event System.Action? Saved;
public void Save(PluginData pluginData)
{
AllTagsChanges = pluginData.AllTags.GetChanges(pluginData.Default.AllTags.GetChanges());
AllRoleTagsChanges = pluginData.AllRoleTags.GetChanges(pluginData.Default.AllRoleTags.GetChanges());
RoleTagsChanges = [];
foreach ((var role, var roleTag) in pluginData.RoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.RoleTags.TryGetValue(role, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = roleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RoleTagsChanges[role] = changes;
}
}
DpsRoleTagsChanges = [];
foreach ((var dpsRole, var dpsRoleTag) in pluginData.DpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = dpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
DpsRoleTagsChanges[dpsRole] = changes;
}
}
RangedDpsRoleTagsChanges = [];
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in pluginData.RangedDpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = rangedDpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RangedDpsRoleTagsChanges[rangedDpsRole] = changes;
}
}
LandHandRoleTagsChanges = [];
foreach ((var landHandRole, var landHandRoleTag) in pluginData.LandHandRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = landHandRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
LandHandRoleTagsChanges[landHandRole] = changes;
}
}
JobTagsChanges = [];
foreach ((var jobAbbreviation, var jobTag) in pluginData.JobTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.JobTags.TryGetValue(jobAbbreviation, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = jobTag.GetChanges(defaultChanges);
if (changes.Any())
{
JobTagsChanges[jobAbbreviation] = changes;
}
}
AllCustomTagsChanges = pluginData.AllCustomTags.GetChanges(pluginData.Default.AllCustomTags.GetChanges());
CustomTagsChanges = [];
foreach (var customTag in pluginData.CustomTags)
{
CustomTagsChanges.Add(customTag.GetChanges());
}
Identities = pluginData.Identities;
SavePluginConfig();
Saved?.Invoke();
}
private void SavePluginConfig()
{
Version = DEFAULT_CONFIG_VERSION;
var configFilePath = GetConfigFilePath();
var configFileContent = JsonConvert.SerializeObject(this, Formatting.Indented, GetJsonSettings());
File.WriteAllText(configFilePath, configFileContent);
}
private static void BackupPluginConfig()
{
var configFilePath = GetConfigFilePath();
var configFilePathOld = Path.ChangeExtension(configFilePath, ".old" + Path.GetExtension(configFilePath));
File.Copy(configFilePath, configFilePathOld, true);
}
public static PluginConfiguration LoadPluginConfig()
{
var configFilePath = GetConfigFilePath();
object config = null;
if (File.Exists(configFilePath))
{
var configFileContent = File.ReadAllText(configFilePath);
config = JsonConvert.DeserializeObject<PluginConfiguration>(configFileContent, GetJsonSettings());
}
else
{
// Try loading the old settings, if possible
configFilePath = PluginServices.DalamudPluginInterface.ConfigFile.FullName;
config = PluginServices.DalamudPluginInterface.GetPluginConfig();
}
if (config is PluginConfiguration pluginConfig)
{
if (PluginConfigFix(pluginConfig))
{
BackupPluginConfig();
pluginConfig.SavePluginConfig();
}
}
return config as PluginConfiguration;
}
private static string GetConfigFilePath()
{
return Path.Combine(PluginServices.DalamudPluginInterface.ConfigDirectory.FullName, "Config.json");
}
private static JsonSerializerSettings GetJsonSettings()
{
var jsonSettings = new JsonSerializerSettings
{
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
TypeNameHandling = TypeNameHandling.Auto,
};
public DefaultPluginDataTemplate DefaultPluginDataTemplate = DefaultPluginDataTemplate.Simple;
public StatusIconPriorizerSettings StatusIconPriorizerSettings = new(true);
public bool MoveStatusIconToNameplateTextIfPossible = true;
public bool IsPlayerNameRandomlyGenerated = false;
public bool IsCustomTagsContextMenuEnabled = true;
public bool IsShowInheritedPropertiesEnabled = true;
public bool IsPlayersTabOrderedByProximity = false;
public bool IsPlayersTabSelfVisible = true;
public bool IsPlayersTabFriendsVisible = true;
public bool IsPlayersTabPartyVisible = true;
public bool IsPlayersTabAllianceVisible = true;
public bool IsPlayersTabEnemiesVisible = true;
public bool IsPlayersTabOthersVisible = false;
public bool IsGeneralOptionsAllTheSameEnabled = true;
jsonSettings.Converters.Add(new StringEnumConverter());
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllTagsChanges = new Dictionary<string, InheritableData>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllRoleTagsChanges = new Dictionary<string, InheritableData>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<Role, Dictionary<string, InheritableData>> RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<DpsRole, Dictionary<string, InheritableData>> DpsRoleTagsChanges = new Dictionary<DpsRole, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<RangedDpsRole, Dictionary<string, InheritableData>> RangedDpsRoleTagsChanges = new Dictionary<RangedDpsRole, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<LandHandRole, Dictionary<string, InheritableData>> LandHandRoleTagsChanges = new Dictionary<LandHandRole, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, Dictionary<string, InheritableData>> JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllCustomTagsChanges = new Dictionary<string, InheritableData>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Dictionary<string, InheritableData>> CustomTagsChanges = new List<Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Identity> Identities = new List<Identity>();
#region Obsulate Properties
[Obsolete]
[JsonProperty("GeneralOptions")]
private Dictionary<Data.ActivityContext, GeneralOptionsClass> GeneralOptionsV1
{
set
{
GeneralOptions.Clear();
foreach (var kvp in value)
GeneralOptions.Add((ActivityType)kvp.Key, kvp.Value);
}
}
[JsonProperty("NameplateFreeCompanyVisibility"), Obsolete]
private NameplateFreeCompanyVisibility NameplateFreeCompanyVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateFreeCompanyVisibility = value;
}
}
[JsonProperty("NameplateTitleVisibility"), Obsolete]
public NameplateTitleVisibility NameplateTitleVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitleVisibility = value;
}
}
[JsonProperty("NameplateTitlePosition"), Obsolete]
public NameplateTitlePosition NameplateTitlePositionV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitlePosition = value;
}
}
[JsonProperty("IsApplyTagsToAllChatMessagesEnabled"), Obsolete]
private bool IsApplyTagsToAllChatMessagesEnabledV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].IsApplyTagsToAllChatMessagesEnabled = value;
}
}
#endregion
public event System.Action? Saved;
public void Save(PluginData pluginData)
{
AllTagsChanges = pluginData.AllTags.GetChanges(pluginData.Default.AllTags.GetChanges());
AllRoleTagsChanges = pluginData.AllRoleTags.GetChanges(pluginData.Default.AllRoleTags.GetChanges());
RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
foreach ((var role, var roleTag) in pluginData.RoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.RoleTags.TryGetValue(role, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = roleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RoleTagsChanges[role] = changes;
}
}
DpsRoleTagsChanges = new Dictionary<DpsRole, Dictionary<string, InheritableData>>();
foreach ((var dpsRole, var dpsRoleTag) in pluginData.DpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = dpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
DpsRoleTagsChanges[dpsRole] = changes;
}
}
RangedDpsRoleTagsChanges = new Dictionary<RangedDpsRole, Dictionary<string, InheritableData>>();
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in pluginData.RangedDpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = rangedDpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RangedDpsRoleTagsChanges[rangedDpsRole] = changes;
}
}
LandHandRoleTagsChanges = new Dictionary<LandHandRole, Dictionary<string, InheritableData>>();
foreach ((var landHandRole, var landHandRoleTag) in pluginData.LandHandRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = landHandRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
LandHandRoleTagsChanges[landHandRole] = changes;
}
}
JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
foreach ((var jobAbbreviation, var jobTag) in pluginData.JobTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.JobTags.TryGetValue(jobAbbreviation, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = jobTag.GetChanges(defaultChanges);
if (changes.Any())
{
JobTagsChanges[jobAbbreviation] = changes;
}
}
AllCustomTagsChanges = pluginData.AllCustomTags.GetChanges(pluginData.Default.AllCustomTags.GetChanges());
CustomTagsChanges = new List<Dictionary<string, InheritableData>>();
foreach (var customTag in pluginData.CustomTags)
{
CustomTagsChanges.Add(customTag.GetChanges());
}
Identities = pluginData.Identities;
SavePluginConfig();
Saved?.Invoke();
}
private void SavePluginConfig()
{
Version = DEFAULT_CONFIG_VERSION;
var configFilePath = GetConfigFilePath();
var configFileContent = JsonConvert.SerializeObject(this, Formatting.Indented, GetJsonSettings());
File.WriteAllText(configFilePath, configFileContent);
}
public static PluginConfiguration LoadPluginConfig()
{
var configFilePath = GetConfigFilePath();
object config = null;
if (File.Exists(configFilePath))
{
var configFileContent = File.ReadAllText(configFilePath);
config = JsonConvert.DeserializeObject<PluginConfiguration>(configFileContent, GetJsonSettings());
}
else
{
// Try loading the old settings, if possible
configFilePath = PluginServices.DalamudPluginInterface.ConfigFile.FullName;
config = PluginServices.DalamudPluginInterface.GetPluginConfig();
}
return config as PluginConfiguration;
}
private static string GetConfigFilePath()
{
return Path.Combine(PluginServices.DalamudPluginInterface.ConfigDirectory.FullName, "Config.json");
}
private static JsonSerializerSettings GetJsonSettings()
{
var jsonSettings = new JsonSerializerSettings
{
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
TypeNameHandling = TypeNameHandling.Auto,
};
jsonSettings.Converters.Add(new StringEnumConverter());
return jsonSettings;
}
return jsonSettings;
}
public class GeneralOptionsClass
private static bool PluginConfigFix(PluginConfiguration config)
{
public NameplateFreeCompanyVisibility NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility.Default;
public NameplateTitleVisibility NameplateTitleVisibility = NameplateTitleVisibility.WhenHasTags;
public NameplateTitlePosition NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName;
public DeadPlayerHandling NameplateDeadPlayerHandling = DeadPlayerHandling.Include;
public bool IsApplyTagsToAllChatMessagesEnabled = true;
bool hasFixes = false;
// Patch 6.4 - Disable all Job & Role specific colors & prefix
// Not used yet, but keeping it there, just for the case,
//if (config.Version <= 1)
//{
// void fixTags(Dictionary<string, InheritableData> dic)
// {
// foreach (var change in config.AllRoleTagsChanges.ToArray())
// {
// var key = change.Key;
// if (key == nameof(Tag.IsTextVisibleInChat) ||
// key == nameof(Tag.IsTextVisibleInNameplates) ||
// key == nameof(Tag.IsRoleIconVisibleInChat) ||
// key == nameof(Tag.IsRoleIconVisibleInNameplates) ||
// key == nameof(Tag.IsTextColorAppliedToNameplateName) ||
// key == nameof(Tag.IsTextColorAppliedToChatName) ||
// key == nameof(Tag.IsJobIconVisibleInNameplates))
// {
// var data = change.Value;
// data.Behavior = InheritableBehavior.Disabled;
// }
// }
// }
// // "All Roles" tag changes
// fixTags(config.AllRoleTagsChanges);
// // Role tags changes
// foreach (var kvp in config.RoleTagsChanges)
// fixTags(kvp.Value);
// // Job tags changes
// foreach (var kvp in config.JobTagsChanges)
// fixTags(kvp.Value);
// hasFixes = true;
//}
return hasFixes;
}
}
public class GeneralOptionsClass
{
public NameplateFreeCompanyVisibility NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility.Default;
public NameplateTitleVisibility NameplateTitleVisibility = NameplateTitleVisibility.WhenHasTags;
public NameplateTitlePosition NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName;
public DeadPlayerHandling NameplateDeadPlayerHandling = DeadPlayerHandling.Include;
public bool IsApplyTagsToAllChatMessagesEnabled = true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,14 @@
using Newtonsoft.Json;
using System;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[Obsolete]
[Flags]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum ActivityContext
{
[Obsolete]
[Flags]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum ActivityContext
{
None = 0x0,
PveDuty = 0x1,
PvpDuty = 0x2,
}
None = 0x0,
PveDuty = 0x1,
PvpDuty = 0x2,
}

View File

@@ -1,23 +1,22 @@
using Pilz.Dalamud.ActivityContexts;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public static class ActivityContextHelper
{
public static class ActivityContextHelper
public static bool GetIsVisible(ActivityType playerContext, bool desiredPveDutyVisibility, bool desiredPvpDutyVisibility, bool desiredOthersVisibility)
{
public static bool GetIsVisible(ActivityType playerContext, bool desiredPveDutyVisibility, bool desiredPvpDutyVisibility, bool desiredOthersVisibility)
{
bool isVisible = false;
bool isVisible = false;
if (playerContext.HasFlag(ActivityType.PveDuty))
isVisible |= desiredPveDutyVisibility;
if (playerContext.HasFlag(ActivityType.PveDuty))
isVisible |= desiredPveDutyVisibility;
if (playerContext.HasFlag(ActivityType.PvpDuty))
isVisible |= desiredPvpDutyVisibility;
if (playerContext.HasFlag(ActivityType.PvpDuty))
isVisible |= desiredPvpDutyVisibility;
if (playerContext == ActivityType.None)
isVisible |= desiredOthersVisibility;
if (playerContext == ActivityType.None)
isVisible |= desiredOthersVisibility;
return isVisible;
}
return isVisible;
}
}

View File

@@ -1,15 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Data;
namespace PlayerTags.Data
public enum DeadPlayerHandling
{
public enum DeadPlayerHandling
{
Ignore,
Include,
GrayOut
}
Ignore,
Include,
GrayOut
}

View File

@@ -5,488 +5,486 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class DefaultPluginData
{
public class DefaultPluginData
public Tag AllTags { get; private set; }
public Tag AllRoleTags { get; private set; }
public Dictionary<Role, Tag> RoleTags { get; private set; }
public Dictionary<DpsRole, Tag> DpsRoleTags { get; private set; }
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags { get; private set; }
public Dictionary<LandHandRole, Tag> LandHandRoleTags { get; private set; }
public Dictionary<string, Tag> JobTags { get; private set; }
public Tag AllCustomTags { get; private set; }
public DefaultPluginData(DefaultPluginDataTemplate template)
{
public Tag AllTags { get; private set; }
SetupTemplate(template);
}
public Tag AllRoleTags { get; private set; }
public Dictionary<Role, Tag> RoleTags { get; private set; }
public Dictionary<DpsRole, Tag> DpsRoleTags { get; private set; }
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags { get; private set; }
public Dictionary<LandHandRole, Tag> LandHandRoleTags { get; private set; }
public Dictionary<string, Tag> JobTags { get; private set; }
private void SetupTemplate(DefaultPluginDataTemplate template)
{
Clear();
public Tag AllCustomTags { get; private set; }
public DefaultPluginData(DefaultPluginDataTemplate template)
switch (template)
{
SetupTemplate(template);
case DefaultPluginDataTemplate.None:
SetupTemplateNone();
break;
case DefaultPluginDataTemplate.Basic:
SetupTemplateBasic();
break;
case DefaultPluginDataTemplate.Simple:
SetupTemplateSimple();
break;
case DefaultPluginDataTemplate.Full:
SetupTemplateFull();
break;
}
private void SetupTemplate(DefaultPluginDataTemplate template)
SetupJobTags();
}
private void Clear()
{
RoleTags = [];
DpsRoleTags = [];
RangedDpsRoleTags = [];
LandHandRoleTags = [];
}
private void SetupTemplateNone()
{
AllTags = new Tag()
{
Clear();
IsSelected = true,
IsExpanded = true,
};
switch(template)
{
case DefaultPluginDataTemplate.None:
SetupTemplateNone();
break;
case DefaultPluginDataTemplate.Basic:
SetupTemplateBasic();
break;
case DefaultPluginDataTemplate.Simple:
SetupTemplateSimple();
break;
case DefaultPluginDataTemplate.Full:
SetupTemplateFull();
break;
}
SetupJobTags();
}
private void Clear()
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RoleTags = new Dictionary<Role, Tag>();
DpsRoleTags = new Dictionary<DpsRole, Tag>();
RangedDpsRoleTags = new Dictionary<RangedDpsRole, Tag>();
LandHandRoleTags = new Dictionary<LandHandRole, Tag>();
}
private void SetupTemplateNone()
RoleTags[Role.LandHand] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
};
IsSelected = false,
IsExpanded = false,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
}
private void SetupTemplateBasic()
RoleTags[Role.Tank] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
IsSelected = false,
IsExpanded = false,
};
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateSimple()
RoleTags[Role.Healer] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsSelected = false,
IsExpanded = false,
};
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToChatName = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateFull()
RoleTags[Role.Dps] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsSelected = false,
IsExpanded = true,
};
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToNameplateName = true,
IsTextColorAppliedToChatName = true,
IsJobIconVisibleInNameplates = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupJobTags()
DpsRoleTags[DpsRole.Melee] = new Tag()
{
JobTags = new Dictionary<string, Tag>();
IsSelected = false,
IsExpanded = false,
};
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
}
private void SetupTemplateBasic()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateSimple()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToChatName = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateFull()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToNameplateName = true,
IsTextColorAppliedToChatName = true,
IsJobIconVisibleInNameplates = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupJobTags()
{
JobTags = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach ((var role, var roleTagChanges) in RoleTags)
{
foreach ((var role, var roleTagChanges) in RoleTags)
foreach (var classJob in classJobs.Where(classJob => RoleHelper.RolesByRoleId[classJob.Role] == role && !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
foreach (var classJob in classJobs.Where(classJob => RoleHelper.RolesByRoleId[classJob.Role] == role && !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
if (!JobTags.ContainsKey(classJob.Abbreviation.RawString))
{
if (!JobTags.ContainsKey(classJob.Abbreviation.RawString))
JobTags[classJob.Abbreviation.RawString] = new Tag()
{
JobTags[classJob.Abbreviation.RawString] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Text = classJob.Abbreviation.RawString,
};
}
IsSelected = false,
IsExpanded = false,
Text = classJob.Abbreviation.RawString,
};
}
}
}

View File

@@ -1,16 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Data;
namespace PlayerTags.Data
public enum DefaultPluginDataTemplate
{
public enum DefaultPluginDataTemplate
{
None,
Basic,
Simple,
Full
}
None,
Basic,
Simple,
Full
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum DpsRole
{
public enum DpsRole
{
Melee,
Ranged
}
Melee,
Ranged
}

View File

@@ -2,100 +2,99 @@
using System;
using System.Collections.Generic;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class Identity : IComparable<Identity>, IEquatable<Identity>
{
public class Identity : IComparable<Identity>, IEquatable<Identity>
public string Name { get; init; }
public uint? WorldId { get; set; } = null;
public List<Guid> CustomTagIds { get; init; } = [];
[JsonIgnore]
public string? WorldName => WorldHelper.GetWorldName(WorldId);
public Identity(string name)
{
public string Name { get; init; }
public uint? WorldId { get; set; } = null;
public List<Guid> CustomTagIds { get; init; } = new List<Guid>();
Name = name;
}
[JsonIgnore]
public string? WorldName => WorldHelper.GetWorldName(WorldId);
public override string ToString()
{
string str = Name;
public Identity(string name)
if (WorldId != null)
{
Name = name;
str += $"@{WorldName}";
}
public override string ToString()
return str;
}
public int CompareTo(Identity? other)
{
string? otherToString = null;
if (!(other is null))
{
string str = Name;
if (WorldId != null)
{
str += $"@{WorldName}";
}
return str;
otherToString = other.ToString();
}
public int CompareTo(Identity? other)
{
string? otherToString = null;
if (!(other is null))
{
otherToString = other.ToString();
}
return ToString().CompareTo(otherToString);
}
return ToString().CompareTo(otherToString);
public override bool Equals(object? obj)
{
return obj is Identity identity && Equals(identity);
}
public bool Equals(Identity? obj)
{
if (obj is null)
{
return false;
}
public override bool Equals(object? obj)
return this == obj;
}
public static bool operator ==(Identity? first, Identity? second)
{
if (ReferenceEquals(first, second))
{
return obj is Identity identity && Equals(identity);
return true;
}
public bool Equals(Identity? obj)
if (first is null && second is null)
{
if (obj is null)
{
return false;
}
return this == obj;
return true;
}
public static bool operator ==(Identity? first, Identity? second)
if (first is null || second is null)
{
if (ReferenceEquals(first, second))
{
return true;
}
if (first is null && second is null)
{
return true;
}
if (first is null || second is null)
{
return false;
}
bool areNamesEqual = first.Name.ToLower().Trim() == second.Name.ToLower().Trim();
// If one of the worlds are null then it's technically equal as it could be promoted to the identity that does have a world
bool areWorldsEqual = first.WorldId == null || second.WorldId == null || first.WorldId == second.WorldId;
return areNamesEqual && areWorldsEqual;
return false;
}
public static bool operator !=(Identity? first, Identity? second)
bool areNamesEqual = first.Name.ToLower().Trim() == second.Name.ToLower().Trim();
// If one of the worlds are null then it's technically equal as it could be promoted to the identity that does have a world
bool areWorldsEqual = first.WorldId == null || second.WorldId == null || first.WorldId == second.WorldId;
return areNamesEqual && areWorldsEqual;
}
public static bool operator !=(Identity? first, Identity? second)
{
return !(first == second);
}
public override int GetHashCode()
{
var hashCode = Name.GetHashCode();
if (WorldName != null)
{
return !(first == second);
hashCode *= 17 ^ WorldName.GetHashCode();
}
public override int GetHashCode()
{
var hashCode = Name.GetHashCode();
if (WorldName != null)
{
hashCode *= 17 ^ WorldName.GetHashCode();
}
return hashCode;
}
return hashCode;
}
}

View File

@@ -1,14 +1,13 @@
using System;
namespace PlayerTags.Data
{
public class InheritableCategoryAttribute : Attribute
{
public string CategoryId { get; private set; }
namespace PlayerTags.Data;
public InheritableCategoryAttribute(string categoryId)
{
CategoryId = categoryId;
}
public class InheritableCategoryAttribute : Attribute
{
public string CategoryId { get; private set; }
public InheritableCategoryAttribute(string categoryId)
{
CategoryId = categoryId;
}
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum LandHandRole
{
public enum LandHandRole
{
Land,
Hand
}
Land,
Hand
}

View File

@@ -1,9 +1,8 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum NameplateElement
{
public enum NameplateElement
{
Name,
Title,
FreeCompany
}
Name,
Title,
FreeCompany,
}

View File

@@ -1,11 +1,10 @@
using Newtonsoft.Json;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateFreeCompanyVisibility
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateFreeCompanyVisibility
{
Default,
Never
}
Default,
Never
}

View File

@@ -1,12 +1,11 @@
using Newtonsoft.Json;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitlePosition
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitlePosition
{
Default,
AlwaysAboveName,
AlwaysBelowName
}
Default,
AlwaysAboveName,
AlwaysBelowName
}

View File

@@ -1,13 +1,12 @@
using Newtonsoft.Json;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitleVisibility
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitleVisibility
{
Default,
Always,
Never,
WhenHasTags
}
Default,
Always,
Never,
WhenHasTags
}

View File

@@ -1,12 +1,11 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum PlayerContext
{
public enum PlayerContext
{
None = 0x0,
Self = 0x1,
Party = 0x2,
Alliance = 0x4,
Enemy = 0x8,
Friend = 0x10
}
None = 0x0,
Self = 0x1,
Party = 0x2,
Alliance = 0x4,
Enemy = 0x8,
Friend = 0x10
}

View File

@@ -1,81 +1,59 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public static class PlayerContextHelper
{
public static class PlayerContextHelper
public static PlayerContext GetPlayerContext(IPlayerCharacter playerCharacter)
{
public static PlayerContext GetPlayerContext(PlayerCharacter playerCharacter)
{
PlayerContext playerContext = PlayerContext.None;
var playerContext = PlayerContext.None;
if (PluginServices.ClientState.LocalPlayer == playerCharacter)
{
playerContext |= PlayerContext.Self;
}
if (PluginServices.ClientState.LocalPlayer == playerCharacter)
playerContext |= PlayerContext.Self;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend))
{
playerContext |= PlayerContext.Friend;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend))
playerContext |= PlayerContext.Friend;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember))
{
playerContext |= PlayerContext.Party;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember))
playerContext |= PlayerContext.Party;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.AllianceMember))
{
playerContext |= PlayerContext.Alliance;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.AllianceMember))
playerContext |= PlayerContext.Alliance;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Hostile))
{
playerContext |= PlayerContext.Enemy;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Hostile))
playerContext |= PlayerContext.Enemy;
return playerContext;
}
return playerContext;
}
public static bool GetIsVisible(PlayerContext playerContext, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
if (playerContext.HasFlag(PlayerContext.Self))
{
return desiredSelfVisibility;
}
public static bool GetIsVisible(PlayerContext playerContext, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
if (playerContext.HasFlag(PlayerContext.Self))
return desiredSelfVisibility;
bool isVisible = false;
if (playerContext.HasFlag(PlayerContext.Friend))
{
isVisible |= desiredFriendsVisibility;
}
var isVisible = false;
if (playerContext.HasFlag(PlayerContext.Party))
{
isVisible |= desiredPartyVisibility;
}
if (playerContext.HasFlag(PlayerContext.Friend))
isVisible |= desiredFriendsVisibility;
if (!playerContext.HasFlag(PlayerContext.Party) && playerContext.HasFlag(PlayerContext.Alliance))
{
isVisible |= desiredAllianceVisibility;
}
if (playerContext.HasFlag(PlayerContext.Party))
isVisible |= desiredPartyVisibility;
if (playerContext.HasFlag(PlayerContext.Enemy))
{
isVisible |= desiredEnemiesVisibility;
}
if (!playerContext.HasFlag(PlayerContext.Party) && playerContext.HasFlag(PlayerContext.Alliance))
isVisible |= desiredAllianceVisibility;
if (playerContext == PlayerContext.None)
{
isVisible |= desiredOthersVisibility;
}
if (playerContext.HasFlag(PlayerContext.Enemy))
isVisible |= desiredEnemiesVisibility;
return isVisible;
}
if (playerContext == PlayerContext.None)
isVisible |= desiredOthersVisibility;
public static bool GetIsVisible(PlayerCharacter playerCharacter, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
return GetIsVisible(GetPlayerContext(playerCharacter), desiredSelfVisibility, desiredFriendsVisibility, desiredPartyVisibility, desiredAllianceVisibility, desiredEnemiesVisibility, desiredOthersVisibility);
}
return isVisible;
}
public static bool GetIsVisible(IPlayerCharacter playerCharacter, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
return GetIsVisible(GetPlayerContext(playerCharacter), desiredSelfVisibility, desiredFriendsVisibility, desiredPartyVisibility, desiredAllianceVisibility, desiredEnemiesVisibility, desiredOthersVisibility);
}
}

View File

@@ -1,307 +1,305 @@
using Dalamud.ContextMenu;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Party;
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Logging;
using PlayerTags.Configuration;
using PlayerTags.PluginStrings;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class PluginData
{
public class PluginData
public DefaultPluginData Default;
public Tag AllTags;
public Tag AllRoleTags;
public Dictionary<Role, Tag> RoleTags;
public Dictionary<DpsRole, Tag> DpsRoleTags;
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags;
public Dictionary<LandHandRole, Tag> LandHandRoleTags;
public Dictionary<string, Tag> JobTags;
public Tag AllCustomTags;
public List<Tag> CustomTags;
public List<Identity> Identities;
private PluginConfiguration pluginConfiguration;
public PluginData(PluginConfiguration pluginConfiguration)
{
public DefaultPluginData Default;
public Tag AllTags;
public Tag AllRoleTags;
public Dictionary<Role, Tag> RoleTags;
public Dictionary<DpsRole, Tag> DpsRoleTags;
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags;
public Dictionary<LandHandRole, Tag> LandHandRoleTags;
public Dictionary<string, Tag> JobTags;
public Tag AllCustomTags;
public List<Tag> CustomTags;
public List<Identity> Identities;
this.pluginConfiguration = pluginConfiguration;
ReloadDefault();
}
private PluginConfiguration pluginConfiguration;
public void ReloadDefault()
{
Default = new DefaultPluginData(pluginConfiguration.DefaultPluginDataTemplate);
public PluginData(PluginConfiguration pluginConfiguration)
// Set the default changes and saved changes
AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)), Default.AllTags);
AllTags.SetChanges(pluginConfiguration.AllTagsChanges);
AllRoleTags = new Tag(new LocalizedPluginString(nameof(AllRoleTags)), Default.AllRoleTags);
AllRoleTags.SetChanges(pluginConfiguration.AllRoleTagsChanges);
RoleTags = [];
foreach (var role in Enum.GetValues<Role>())
{
this.pluginConfiguration = pluginConfiguration;
ReloadDefault();
if (Default.RoleTags.TryGetValue(role, out var defaultTag))
{
RoleTags[role] = new Tag(new LocalizedPluginString(Localizer.GetName(role)), defaultTag);
if (pluginConfiguration.RoleTagsChanges.TryGetValue(role, out var savedChanges))
{
RoleTags[role].SetChanges(savedChanges);
}
}
}
public void ReloadDefault()
DpsRoleTags = [];
foreach (var dpsRole in Enum.GetValues<DpsRole>())
{
Default = new DefaultPluginData(pluginConfiguration.DefaultPluginDataTemplate);
// Set the default changes and saved changes
AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)), Default.AllTags);
AllTags.SetChanges(pluginConfiguration.AllTagsChanges);
AllRoleTags = new Tag(new LocalizedPluginString(nameof(AllRoleTags)), Default.AllRoleTags);
AllRoleTags.SetChanges(pluginConfiguration.AllRoleTagsChanges);
RoleTags = new Dictionary<Role, Tag>();
foreach (var role in Enum.GetValues<Role>())
if (Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
{
if (Default.RoleTags.TryGetValue(role, out var defaultTag))
DpsRoleTags[dpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(dpsRole)), defaultTag);
if (pluginConfiguration.DpsRoleTagsChanges.TryGetValue(dpsRole, out var savedChanges))
{
RoleTags[role] = new Tag(new LocalizedPluginString(Localizer.GetName(role)), defaultTag);
if (pluginConfiguration.RoleTagsChanges.TryGetValue(role, out var savedChanges))
{
RoleTags[role].SetChanges(savedChanges);
}
DpsRoleTags[dpsRole].SetChanges(savedChanges);
}
}
}
DpsRoleTags = new Dictionary<DpsRole, Tag>();
foreach (var dpsRole in Enum.GetValues<DpsRole>())
RangedDpsRoleTags = [];
foreach (var rangedDpsRole in Enum.GetValues<RangedDpsRole>())
{
if (Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
{
if (Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
RangedDpsRoleTags[rangedDpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(rangedDpsRole)), defaultTag);
if (pluginConfiguration.RangedDpsRoleTagsChanges.TryGetValue(rangedDpsRole, out var savedChanges))
{
DpsRoleTags[dpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(dpsRole)), defaultTag);
if (pluginConfiguration.DpsRoleTagsChanges.TryGetValue(dpsRole, out var savedChanges))
{
DpsRoleTags[dpsRole].SetChanges(savedChanges);
}
RangedDpsRoleTags[rangedDpsRole].SetChanges(savedChanges);
}
}
}
RangedDpsRoleTags = new Dictionary<RangedDpsRole, Tag>();
foreach (var rangedDpsRole in Enum.GetValues<RangedDpsRole>())
LandHandRoleTags = [];
foreach (var landHandRole in Enum.GetValues<LandHandRole>())
{
if (Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultChanges))
{
if (Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
LandHandRoleTags[landHandRole] = new Tag(new LocalizedPluginString(Localizer.GetName(landHandRole)), defaultChanges);
if (pluginConfiguration.LandHandRoleTagsChanges.TryGetValue(landHandRole, out var savedChanges))
{
RangedDpsRoleTags[rangedDpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(rangedDpsRole)), defaultTag);
if (pluginConfiguration.RangedDpsRoleTagsChanges.TryGetValue(rangedDpsRole, out var savedChanges))
{
RangedDpsRoleTags[rangedDpsRole].SetChanges(savedChanges);
}
LandHandRoleTags[landHandRole].SetChanges(savedChanges);
}
}
}
LandHandRoleTags = new Dictionary<LandHandRole, Tag>();
foreach (var landHandRole in Enum.GetValues<LandHandRole>())
JobTags = [];
foreach ((var jobAbbreviation, var role) in RoleHelper.RolesByJobAbbreviation)
{
if (Default.JobTags.TryGetValue(jobAbbreviation, out var defaultChanges))
{
if (Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultChanges))
JobTags[jobAbbreviation] = new Tag(new LiteralPluginString(jobAbbreviation), defaultChanges);
if (pluginConfiguration.JobTagsChanges.TryGetValue(jobAbbreviation, out var savedChanges))
{
LandHandRoleTags[landHandRole] = new Tag(new LocalizedPluginString(Localizer.GetName(landHandRole)), defaultChanges);
if (pluginConfiguration.LandHandRoleTagsChanges.TryGetValue(landHandRole, out var savedChanges))
{
LandHandRoleTags[landHandRole].SetChanges(savedChanges);
}
JobTags[jobAbbreviation].SetChanges(savedChanges);
}
}
}
JobTags = new Dictionary<string, Tag>();
foreach ((var jobAbbreviation, var role) in RoleHelper.RolesByJobAbbreviation)
AllCustomTags = new Tag(new LocalizedPluginString(nameof(AllCustomTags)), Default.AllCustomTags);
AllCustomTags.SetChanges(pluginConfiguration.AllCustomTagsChanges);
CustomTags = [];
foreach (var savedChanges in pluginConfiguration.CustomTagsChanges)
{
var tag = new Tag(new LocalizedPluginString(nameof(CustomTags)));
tag.SetChanges(savedChanges);
CustomTags.Add(tag);
}
// Set up the inheritance heirarchy
AllRoleTags.Parent = AllTags;
foreach ((var role, var roleTag) in RoleTags)
{
roleTag.Parent = AllRoleTags;
if (role == Role.Dps)
{
if (Default.JobTags.TryGetValue(jobAbbreviation, out var defaultChanges))
foreach ((var dpsRole, var dpsRoleTag) in DpsRoleTags)
{
JobTags[jobAbbreviation] = new Tag(new LiteralPluginString(jobAbbreviation), defaultChanges);
if (pluginConfiguration.JobTagsChanges.TryGetValue(jobAbbreviation, out var savedChanges))
dpsRoleTag.Parent = roleTag;
if (dpsRole == DpsRole.Ranged)
{
JobTags[jobAbbreviation].SetChanges(savedChanges);
}
}
}
AllCustomTags = new Tag(new LocalizedPluginString(nameof(AllCustomTags)), Default.AllCustomTags);
AllCustomTags.SetChanges(pluginConfiguration.AllCustomTagsChanges);
CustomTags = new List<Tag>();
foreach (var savedChanges in pluginConfiguration.CustomTagsChanges)
{
var tag = new Tag(new LocalizedPluginString(nameof(CustomTags)));
tag.SetChanges(savedChanges);
CustomTags.Add(tag);
}
// Set up the inheritance heirarchy
AllRoleTags.Parent = AllTags;
foreach ((var role, var roleTag) in RoleTags)
{
roleTag.Parent = AllRoleTags;
if (role == Role.Dps)
{
foreach ((var dpsRole, var dpsRoleTag) in DpsRoleTags)
{
dpsRoleTag.Parent = roleTag;
if (dpsRole == DpsRole.Ranged)
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in RangedDpsRoleTags)
{
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in RangedDpsRoleTags)
{
rangedDpsRoleTag.Parent = dpsRoleTag;
}
rangedDpsRoleTag.Parent = dpsRoleTag;
}
}
}
else if (role == Role.LandHand)
}
else if (role == Role.LandHand)
{
foreach ((var landHandRole, var landHandRoleTag) in LandHandRoleTags)
{
foreach ((var landHandRole, var landHandRoleTag) in LandHandRoleTags)
{
landHandRoleTag.Parent = roleTag;
}
landHandRoleTag.Parent = roleTag;
}
}
}
foreach ((var jobAbbreviation, var jobTag) in JobTags)
foreach ((var jobAbbreviation, var jobTag) in JobTags)
{
if (RoleHelper.RolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var role))
{
if (RoleHelper.RolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var role))
if (RoleHelper.DpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var dpsRole))
{
if (RoleHelper.DpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var dpsRole))
if (RoleHelper.RangedDpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var rangedDpsRole))
{
if (RoleHelper.RangedDpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var rangedDpsRole))
{
jobTag.Parent = RangedDpsRoleTags[rangedDpsRole];
}
else
{
jobTag.Parent = DpsRoleTags[dpsRole];
}
}
else if (RoleHelper.LandHandRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var landHandRole))
{
jobTag.Parent = LandHandRoleTags[landHandRole];
jobTag.Parent = RangedDpsRoleTags[rangedDpsRole];
}
else
{
jobTag.Parent = RoleTags[RoleHelper.RolesByJobAbbreviation[jobAbbreviation]];
jobTag.Parent = DpsRoleTags[dpsRole];
}
}
else if (RoleHelper.LandHandRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var landHandRole))
{
jobTag.Parent = LandHandRoleTags[landHandRole];
}
else
{
jobTag.Parent = RoleTags[RoleHelper.RolesByJobAbbreviation[jobAbbreviation]];
}
}
}
AllCustomTags.Parent = AllTags;
foreach (var tag in CustomTags)
AllCustomTags.Parent = AllTags;
foreach (var tag in CustomTags)
{
tag.Parent = AllCustomTags;
}
Identities = pluginConfiguration.Identities;
// Migrate old custom tag identity assignments
bool customTagsMigrated = false;
foreach (var customTag in CustomTags)
{
if (customTag.CustomId.Value == Guid.Empty)
{
tag.Parent = AllCustomTags;
customTag.CustomId.Behavior = Inheritables.InheritableBehavior.Enabled;
customTag.CustomId.Value = Guid.NewGuid();
customTagsMigrated = true;
}
Identities = pluginConfiguration.Identities;
// Migrate old custom tag identity assignments
bool customTagsMigrated = false;
foreach (var customTag in CustomTags)
foreach (string identityToAddTo in customTag.IdentitiesToAddTo)
{
if (customTag.CustomId.Value == Guid.Empty)
Identity? identity = Identities.FirstOrDefault(identity => identity.Name.ToLower() == identityToAddTo.ToLower());
if (identity == null)
{
customTag.CustomId.Behavior = Inheritables.InheritableBehavior.Enabled;
customTag.CustomId.Value = Guid.NewGuid();
customTagsMigrated = true;
identity = new Identity(identityToAddTo);
Identities.Add(identity);
}
foreach (string identityToAddTo in customTag.IdentitiesToAddTo)
if (identity != null)
{
Identity? identity = Identities.FirstOrDefault(identity => identity.Name.ToLower() == identityToAddTo.ToLower());
if (identity == null)
{
identity = new Identity(identityToAddTo);
Identities.Add(identity);
}
if (identity != null)
{
identity.CustomTagIds.Add(customTag.CustomId.Value);
customTagsMigrated = true;
}
}
if (customTag.GameObjectNamesToApplyTo.Behavior != Inheritables.InheritableBehavior.Inherit)
{
customTag.GameObjectNamesToApplyTo.Behavior = Inheritables.InheritableBehavior.Inherit;
customTag.GameObjectNamesToApplyTo.Value = "";
identity.CustomTagIds.Add(customTag.CustomId.Value);
customTagsMigrated = true;
}
}
if (customTagsMigrated)
if (customTag.GameObjectNamesToApplyTo.Behavior != Inheritables.InheritableBehavior.Inherit)
{
pluginConfiguration.Save(this);
customTag.GameObjectNamesToApplyTo.Behavior = Inheritables.InheritableBehavior.Inherit;
customTag.GameObjectNamesToApplyTo.Value = "";
customTagsMigrated = true;
}
}
public void AddCustomTagToIdentity(Tag customTag, Identity identity)
if (customTagsMigrated)
{
if (!identity.CustomTagIds.Contains(customTag.CustomId.Value))
{
identity.CustomTagIds.Add(customTag.CustomId.Value);
}
if (!Identities.Contains(identity))
{
Identities.Add(identity);
}
}
public void RemoveCustomTagFromIdentity(Tag customTag, Identity identity)
{
identity.CustomTagIds.Remove(customTag.CustomId.Value);
if (!identity.CustomTagIds.Any())
{
Identities.Remove(identity);
}
}
public void RemoveCustomTagFromIdentities(Tag customTag)
{
foreach (var identity in Identities.ToArray())
{
RemoveCustomTagFromIdentity(customTag, identity);
}
}
public Identity GetIdentity(string name, uint? worldId)
{
foreach (var identity in Identities)
{
if (identity.Name.ToLower().Trim() == name.ToLower().Trim())
{
if (identity.WorldId == null && worldId != null)
{
identity.WorldId = worldId;
pluginConfiguration.Save(this);
}
return identity;
}
}
return new Identity(name)
{
WorldId = worldId
};
}
public Identity? GetIdentity(GameObjectContextMenuOpenArgs contextMenuOpenedArgs)
{
if (string.IsNullOrEmpty(contextMenuOpenedArgs.Text?.TextValue)
|| contextMenuOpenedArgs.ObjectWorld == 0
|| contextMenuOpenedArgs.ObjectWorld == 65535)
{
return null;
}
return GetIdentity(contextMenuOpenedArgs.Text?.TextValue ?? string.Empty, contextMenuOpenedArgs.ObjectWorld);
}
public Identity GetIdentity(PlayerCharacter playerCharacter)
{
return GetIdentity(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Id);
}
public Identity GetIdentity(PartyMember partyMember)
{
return GetIdentity(partyMember.Name.TextValue, partyMember.World.Id);
}
public Identity GetIdentity(PlayerPayload playerPayload)
{
return GetIdentity(playerPayload.PlayerName, playerPayload.World.RowId);
pluginConfiguration.Save(this);
}
}
public void AddCustomTagToIdentity(Tag customTag, Identity identity)
{
if (!identity.CustomTagIds.Contains(customTag.CustomId.Value))
{
identity.CustomTagIds.Add(customTag.CustomId.Value);
}
if (!Identities.Contains(identity))
{
Identities.Add(identity);
}
}
public void RemoveCustomTagFromIdentity(Tag customTag, Identity identity)
{
identity.CustomTagIds.Remove(customTag.CustomId.Value);
if (!identity.CustomTagIds.Any())
{
Identities.Remove(identity);
}
}
public void RemoveCustomTagFromIdentities(Tag customTag)
{
foreach (var identity in Identities.ToArray())
{
RemoveCustomTagFromIdentity(customTag, identity);
}
}
public Identity GetIdentity(string name, uint? worldId)
{
foreach (var identity in Identities)
{
if (identity.Name.ToLower().Trim() == name.ToLower().Trim())
{
if (identity.WorldId == null && worldId != null)
{
identity.WorldId = worldId;
pluginConfiguration.Save(this);
}
return identity;
}
}
return new Identity(name)
{
WorldId = worldId
};
}
public Identity? GetIdentity(MenuTargetDefault taget)
{
if (string.IsNullOrEmpty(taget.TargetName)
|| taget.TargetHomeWorld.Id == 0
|| taget.TargetHomeWorld.Id == 65535)
{
return null;
}
return GetIdentity(taget.TargetName, taget.TargetHomeWorld.Id);
}
public Identity GetIdentity(IPlayerCharacter playerCharacter)
{
return GetIdentity(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Id);
}
public Identity GetIdentity(IPartyMember partyMember)
{
return GetIdentity(partyMember.Name.TextValue, partyMember.World.Id);
}
public Identity GetIdentity(PlayerPayload playerPayload)
{
return GetIdentity(playerPayload.PlayerName, playerPayload.World.RowId);
}
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum RangedDpsRole
{
public enum RangedDpsRole
{
Magical,
Physical,
}
Magical,
Physical,
}

View File

@@ -1,10 +1,9 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum Role
{
public enum Role
{
LandHand,
Tank,
Healer,
Dps,
}
LandHand,
Tank,
Healer,
Dps,
}

View File

@@ -2,152 +2,151 @@
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public static class RoleHelper
{
public static class RoleHelper
public static Dictionary<byte, Role> RolesByRoleId { get; } = new Dictionary<byte, Role>()
{
public static Dictionary<byte, Role> RolesByRoleId { get; } = new Dictionary<byte, Role>()
{
{ 0, Role.LandHand },
{ 1, Role.Tank },
{ 2, Role.Dps },
{ 3, Role.Dps },
{ 4, Role.Healer },
};
{ 0, Role.LandHand },
{ 1, Role.Tank },
{ 2, Role.Dps },
{ 3, Role.Dps },
{ 4, Role.Healer },
};
public static Dictionary<byte, DpsRole> DpsRolesByRoleId { get; } = new Dictionary<byte, DpsRole>()
{
{ 2, DpsRole.Melee },
{ 3, DpsRole.Ranged },
};
public static Dictionary<byte, DpsRole> DpsRolesByRoleId { get; } = new Dictionary<byte, DpsRole>()
{
{ 2, DpsRole.Melee },
{ 3, DpsRole.Ranged },
};
public static Dictionary<byte, RangedDpsRole> RangedDpsRolesByPrimaryStat { get; } = new Dictionary<byte, RangedDpsRole>()
{
{ 4, RangedDpsRole.Magical },
{ 2, RangedDpsRole.Physical },
};
public static Dictionary<byte, RangedDpsRole> RangedDpsRolesByPrimaryStat { get; } = new Dictionary<byte, RangedDpsRole>()
{
{ 4, RangedDpsRole.Magical },
{ 2, RangedDpsRole.Physical },
};
private static Dictionary<string, Role>? s_RolesByJobAbbreviation = null;
public static Dictionary<string, Role> RolesByJobAbbreviation
private static Dictionary<string, Role>? s_RolesByJobAbbreviation = null;
public static Dictionary<string, Role> RolesByJobAbbreviation
{
get
{
get
if (s_RolesByJobAbbreviation == null)
{
if (s_RolesByJobAbbreviation == null)
{
s_RolesByJobAbbreviation = new Dictionary<string, Role>();
s_RolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
{
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
s_RolesByJobAbbreviation[classJob.Abbreviation] = role;
}
}
}
}
return s_RolesByJobAbbreviation;
}
}
private static Dictionary<string, DpsRole>? s_DpsRolesByJobAbbreviation = null;
public static Dictionary<string, DpsRole> DpsRolesByJobAbbreviation
{
get
{
if (s_DpsRolesByJobAbbreviation == null)
{
s_DpsRolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (DpsRolesByRoleId.TryGetValue(classJob.Role, out var dpsRole))
{
s_DpsRolesByJobAbbreviation[classJob.Abbreviation] = dpsRole;
}
}
}
}
return s_DpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, RangedDpsRole>? s_RangedDpsRolesByJobAbbreviation = null;
public static Dictionary<string, RangedDpsRole> RangedDpsRolesByJobAbbreviation
{
get
{
if (s_RangedDpsRolesByJobAbbreviation == null)
{
s_RangedDpsRolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (DpsRolesByJobAbbreviation.TryGetValue(classJob.Abbreviation, out var dpsRole) && dpsRole == DpsRole.Ranged)
{
if (RangedDpsRolesByPrimaryStat.TryGetValue(classJob.PrimaryStat, out var rangedDPSRole))
{
s_RolesByJobAbbreviation[classJob.Abbreviation] = role;
s_RangedDpsRolesByJobAbbreviation[classJob.Abbreviation] = rangedDPSRole;
}
}
}
}
return s_RolesByJobAbbreviation;
}
return s_RangedDpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, DpsRole>? s_DpsRolesByJobAbbreviation = null;
public static Dictionary<string, DpsRole> DpsRolesByJobAbbreviation
private static Dictionary<string, LandHandRole>? s_LandHandRolesByJobAbbreviation = null;
public static Dictionary<string, LandHandRole> LandHandRolesByJobAbbreviation
{
get
{
get
if (s_LandHandRolesByJobAbbreviation == null)
{
if (s_DpsRolesByJobAbbreviation == null)
s_LandHandRolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
var gatheringSubCategories = PluginServices.DataManager.GetExcelSheet<GatheringSubCategory>();
if (classJobs != null && gatheringSubCategories != null)
{
s_DpsRolesByJobAbbreviation = new Dictionary<string, DpsRole>();
var gatheringJobAbbreviations = gatheringSubCategories
.Select(gatheringSubCategory => gatheringSubCategory.ClassJob.Value)
.Where(classJob => classJob != null)
.Select(classJob => classJob!.Abbreviation).Distinct();
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
{
if (DpsRolesByRoleId.TryGetValue(classJob.Role, out var dpsRole))
if (role == Role.LandHand)
{
s_DpsRolesByJobAbbreviation[classJob.Abbreviation] = dpsRole;
}
}
}
}
return s_DpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, RangedDpsRole>? s_RangedDpsRolesByJobAbbreviation = null;
public static Dictionary<string, RangedDpsRole> RangedDpsRolesByJobAbbreviation
{
get
{
if (s_RangedDpsRolesByJobAbbreviation == null)
{
s_RangedDpsRolesByJobAbbreviation = new Dictionary<string, RangedDpsRole>();
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (DpsRolesByJobAbbreviation.TryGetValue(classJob.Abbreviation, out var dpsRole) && dpsRole == DpsRole.Ranged)
{
if (RangedDpsRolesByPrimaryStat.TryGetValue(classJob.PrimaryStat, out var rangedDPSRole))
if (gatheringJobAbbreviations.Contains(classJob.Abbreviation))
{
s_RangedDpsRolesByJobAbbreviation[classJob.Abbreviation] = rangedDPSRole;
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Land;
}
else
{
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Hand;
}
}
}
}
}
return s_RangedDpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, LandHandRole>? s_LandHandRolesByJobAbbreviation = null;
public static Dictionary<string, LandHandRole> LandHandRolesByJobAbbreviation
{
get
{
if (s_LandHandRolesByJobAbbreviation == null)
{
s_LandHandRolesByJobAbbreviation = new Dictionary<string, LandHandRole>();
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
var gatheringSubCategories = PluginServices.DataManager.GetExcelSheet<GatheringSubCategory>();
if (classJobs != null && gatheringSubCategories != null)
{
var gatheringJobAbbreviations = gatheringSubCategories
.Select(gatheringSubCategory => gatheringSubCategory.ClassJob.Value)
.Where(classJob => classJob != null)
.Select(classJob => classJob!.Abbreviation).Distinct();
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
{
if (role == Role.LandHand)
{
if (gatheringJobAbbreviations.Contains(classJob.Abbreviation))
{
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Land;
}
else
{
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Hand;
}
}
}
}
}
}
return s_LandHandRolesByJobAbbreviation;
}
return s_LandHandRolesByJobAbbreviation;
}
}
}

View File

@@ -1,330 +1,320 @@
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Pilz.Dalamud.Icons;
using PlayerTags.Inheritables;
using PlayerTags.PluginStrings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class Tag
{
public class Tag
public IPluginString Name { get; init; }
[JsonProperty("Parent")]
private Tag? m_Parent = null;
[JsonIgnore]
public Tag? Parent
{
public IPluginString Name { get; init; }
[JsonProperty("Parent")]
private Tag? m_Parent = null;
[JsonIgnore]
public Tag? Parent
get => m_Parent;
set
{
get => m_Parent;
set
if (m_Parent != value)
{
if (m_Parent != value)
if (m_Parent != null)
{
if (m_Parent != null)
if (m_Parent.Children.Contains(this))
{
if (m_Parent.Children.Contains(this))
{
m_Parent.Children.Remove(this);
}
m_Parent.Children.Remove(this);
}
}
m_Parent = value;
if (m_Parent != null)
m_Parent = value;
if (m_Parent != null)
{
m_Parent.Children.Add(this);
foreach ((var name, IInheritable inheritable) in Inheritables)
{
m_Parent.Children.Add(this);
foreach ((var name, IInheritable inheritable) in Inheritables)
{
inheritable.Parent = m_Parent.Inheritables[name];
}
inheritable.Parent = m_Parent.Inheritables[name];
}
}
}
}
}
public List<Tag> Children { get; } = new List<Tag>();
public List<Tag> Children { get; } = [];
[JsonIgnore]
public IEnumerable<Tag> Descendents
[JsonIgnore]
public IEnumerable<Tag> Descendents
{
get
{
get
IEnumerable<Tag> descendents = Children.Prepend(this);
foreach (var child in Children)
{
IEnumerable<Tag> descendents = Children.Prepend(this);
foreach (var child in Children)
{
descendents = descendents.Union(child.Descendents);
}
return descendents.Distinct();
descendents = descendents.Union(child.Descendents);
}
return descendents.Distinct();
}
}
[JsonIgnore]
private Dictionary<string, IInheritable>? m_Inheritables = null;
[JsonIgnore]
public Dictionary<string, IInheritable> Inheritables
[JsonIgnore]
private Dictionary<string, IInheritable>? m_Inheritables = null;
[JsonIgnore]
public Dictionary<string, IInheritable> Inheritables
{
get
{
get
if (m_Inheritables == null)
{
if (m_Inheritables == null)
{
m_Inheritables = new Dictionary<string, IInheritable>();
m_Inheritables = [];
var inheritableFields = GetType().GetFields().Where(field => typeof(IInheritable).IsAssignableFrom(field.FieldType));
foreach (var inheritableField in inheritableFields)
var inheritableFields = GetType().GetFields().Where(field => typeof(IInheritable).IsAssignableFrom(field.FieldType));
foreach (var inheritableField in inheritableFields)
{
IInheritable? inheritable = inheritableField.GetValue(this) as IInheritable;
if (inheritable != null)
{
IInheritable? inheritable = inheritableField.GetValue(this) as IInheritable;
if (inheritable != null)
{
Inheritables[inheritableField.Name] = inheritable;
}
Inheritables[inheritableField.Name] = inheritable;
}
}
return m_Inheritables!;
}
return m_Inheritables!;
}
}
public InheritableValue<bool> IsSelected = new InheritableValue<bool>(false)
public InheritableValue<bool> IsSelected = new(false)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue<bool> IsExpanded = new(false)
{
Behavior = InheritableBehavior.Enabled
};
// Deprecated
public InheritableReference<string> GameObjectNamesToApplyTo = new("");
public InheritableValue<Guid> CustomId = new(Guid.Empty);
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInChat
{
set => IsRoleIconVisibleInChat = value;
}
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInNameplate
{
set => IsRoleIconVisibleInNameplates = value;
}
[InheritableCategory("IconCategory")]
public InheritableValue<BitmapFontIcon> Icon = new(BitmapFontIcon.Aethernet);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInChat = new(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInNameplates = new(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsJobIconVisibleInNameplates = new(false);
[InheritableCategory("IconCategory")]
public InheritableValue<JobIconSetName> JobIconSet = new(JobIconSetName.Framed);
[InheritableCategory("TextCategory")]
public InheritableReference<string> Text = new("");
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextColor = new(6);
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextGlowColor = new(6);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextItalic = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInChat = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInNameplates = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToChatName = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateName = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateTitle = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateFreeCompany = new(false);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInChat = new(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<bool> InsertBehindNumberPrefixInChat = new(true);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInNameplates = new(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<NameplateElement> TagTargetInNameplates = new(NameplateElement.Name);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPveDuties = new(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPvpDuties = new(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInOverworld = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForSelf = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForFriendPlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForPartyPlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForAlliancePlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForEnemyPlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForOtherPlayers = new(false);
[InheritableCategory("ChatFeatureCategory")]
public InheritableReference<List<XivChatType>> TargetChatTypes = new(new List<XivChatType>(Enum.GetValues<XivChatType>()));
[InheritableCategory("ChatFeatureCategory")]
public InheritableValue<bool> TargetChatTypesIncludeUndefined = new(true);
[JsonIgnore]
public string[] IdentitiesToAddTo
{
get
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue<bool> IsExpanded = new InheritableValue<bool>(false)
{
Behavior = InheritableBehavior.Enabled
};
// Deprecated
public InheritableReference<string> GameObjectNamesToApplyTo = new InheritableReference<string>("");
public InheritableValue<Guid> CustomId = new InheritableValue<Guid>(Guid.Empty);
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInChat
{
set => IsRoleIconVisibleInChat = value;
}
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInNameplate
{
set => IsRoleIconVisibleInNameplates = value;
}
[InheritableCategory("IconCategory")]
public InheritableValue<BitmapFontIcon> Icon = new InheritableValue<BitmapFontIcon>(BitmapFontIcon.Aethernet);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInChat = new InheritableValue<bool>(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInNameplates = new InheritableValue<bool>(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsJobIconVisibleInNameplates = new InheritableValue<bool>(false);
[InheritableCategory("IconCategory")]
public InheritableValue<JobIconSetName> JobIconSet = new InheritableValue<JobIconSetName>(JobIconSetName.Framed);
[InheritableCategory("TextCategory")]
public InheritableReference<string> Text = new InheritableReference<string>("");
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextColor = new InheritableValue<ushort>(6);
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextGlowColor = new InheritableValue<ushort>(6);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextItalic = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInChat = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInNameplates = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToChatName = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateName = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateTitle = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateFreeCompany = new InheritableValue<bool>(false);
//[InheritableCategory("NameplateCategory")]
//public InheritableValue<NameplateFreeCompanyVisibility> NameplateFreeCompanyVisibility = new InheritableValue<NameplateFreeCompanyVisibility>(Data.NameplateFreeCompanyVisibility.Default);
//[InheritableCategory("NameplateCategory")]
//public InheritableValue<NameplateTitleVisibility> NameplateTitleVisibility = new InheritableValue<NameplateTitleVisibility>(Data.NameplateTitleVisibility.Default);
//[InheritableCategory("NameplateCategory")]
//public InheritableValue<NameplateTitlePosition> NameplateTitlePosition = new InheritableValue<NameplateTitlePosition>(Data.NameplateTitlePosition.Default);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInChat = new InheritableValue<TagPosition>(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<bool> InsertBehindNumberPrefixInChat = new InheritableValue<bool>(true);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInNameplates = new InheritableValue<TagPosition>(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<NameplateElement> TagTargetInNameplates = new InheritableValue<NameplateElement>(NameplateElement.Name);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPveDuties = new InheritableValue<bool>(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPvpDuties = new InheritableValue<bool>(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInOverworld = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForSelf = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForFriendPlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForPartyPlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForAlliancePlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForEnemyPlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForOtherPlayers = new InheritableValue<bool>(false);
[InheritableCategory("ChatFeatureCategory")]
public InheritableReference<List<XivChatType>> TargetChatTypes = new(new List<XivChatType>(Enum.GetValues<XivChatType>()));
[InheritableCategory("ChatFeatureCategory")]
public InheritableValue<bool> TargetChatTypesIncludeUndefined = new(true);
[JsonIgnore]
public string[] IdentitiesToAddTo
{
get
if (GameObjectNamesToApplyTo == null || GameObjectNamesToApplyTo.InheritedValue == null)
{
if (GameObjectNamesToApplyTo == null || GameObjectNamesToApplyTo.InheritedValue == null)
{
return new string[] { };
}
return GameObjectNamesToApplyTo.InheritedValue.Split(';', ',').Where(item => !string.IsNullOrEmpty(item)).Select(item => item.Trim()).ToArray();
return new string[] { };
}
return GameObjectNamesToApplyTo.InheritedValue.Split(';', ',').Where(item => !string.IsNullOrEmpty(item)).Select(item => item.Trim()).ToArray();
}
}
private Tag? m_Defaults;
[JsonIgnore]
public bool HasDefaults
private Tag? m_Defaults;
[JsonIgnore]
public bool HasDefaults
{
get { return m_Defaults != null; }
}
public Tag()
{
Name = new LiteralPluginString("");
m_Defaults = null;
}
public Tag(IPluginString name)
{
Name = name;
m_Defaults = null;
}
public Tag(IPluginString name, Tag defaults)
{
Name = name;
m_Defaults = defaults;
SetChanges(defaults.GetChanges());
}
public Dictionary<string, InheritableData> GetChanges(Dictionary<string, InheritableData>? defaultChanges = null)
{
Dictionary<string, InheritableData> changes = [];
foreach ((var name, var inheritable) in Inheritables)
{
get { return m_Defaults != null; }
}
public Tag()
{
Name = new LiteralPluginString("");
m_Defaults = null;
}
public Tag(IPluginString name)
{
Name = name;
m_Defaults = null;
}
public Tag(IPluginString name, Tag defaults)
{
Name = name;
m_Defaults = defaults;
SetChanges(defaults.GetChanges());
}
public Dictionary<string, InheritableData> GetChanges(Dictionary<string, InheritableData>? defaultChanges = null)
{
Dictionary<string, InheritableData> changes = new Dictionary<string, InheritableData>();
foreach ((var name, var inheritable) in Inheritables)
// If there's a default for this name, only set the value if it's different from the default
if (defaultChanges != null && defaultChanges.TryGetValue(name, out var defaultInheritableData))
{
// If there's a default for this name, only set the value if it's different from the default
if (defaultChanges != null && defaultChanges.TryGetValue(name, out var defaultInheritableData))
{
var inheritableData = inheritable.GetData();
if (inheritableData.Behavior != defaultInheritableData.Behavior ||
!EqualsInheritableData(inheritableData, defaultInheritableData))
{
changes[name] = inheritable.GetData();
}
}
// If there's no default, then only set the value if it's not inherited
else if (inheritable.Behavior != InheritableBehavior.Inherit)
var inheritableData = inheritable.GetData();
if (inheritableData.Behavior != defaultInheritableData.Behavior ||
!EqualsInheritableData(inheritableData, defaultInheritableData))
{
changes[name] = inheritable.GetData();
}
}
return changes;
}
private static bool EqualsInheritableData(InheritableData data1, InheritableData data2)
{
if (data1.Value is List<XivChatType>)
return EqualsInheritableDataListXivChatType<XivChatType>(data1, data2);
else
return data1.Value.Equals(data2.Value);
}
private static bool EqualsInheritableDataListXivChatType<TEnum>(InheritableData data1, InheritableData data2)
{
var list1 = data1.Value as List<TEnum>;
var list2 = data2.Value as List<TEnum>;
if (list1 is null || list2 is null || list1.Count != list2.Count)
return false;
for (int i = 0; i < list1.Count; i++)
{
if (!list1[i].Equals(list2[i]))
return false;
}
return true;
}
private static readonly Dictionary<string, string> ObsulteInheritableStringMap = new()
{
{ "IsIconVisibleInChat", nameof(IsRoleIconVisibleInChat) },
{ "IsIconVisibleInNameplate", nameof(IsRoleIconVisibleInNameplates) },
{ "IsIconVisibleInNameplates", nameof(IsRoleIconVisibleInNameplates) }
};
private static string FixObsuleteInheritableStringName(string name)
{
if (ObsulteInheritableStringMap.ContainsKey(name))
return ObsulteInheritableStringMap[name];
else
return name;
}
public void SetChanges(IEnumerable<KeyValuePair<string, InheritableData>> changes)
{
foreach ((var name, var inheritableData) in changes)
{
var namefixed = FixObsuleteInheritableStringName(name);
Inheritables[namefixed].SetData(inheritableData);
}
}
private Dictionary<string, InheritableData> GetAllAsChanges()
{
Dictionary<string, InheritableData> changes = new Dictionary<string, InheritableData>();
foreach ((var name, var inheritable) in Inheritables)
// If there's no default, then only set the value if it's not inherited
else if (inheritable.Behavior != InheritableBehavior.Inherit)
{
changes[name] = inheritable.GetData();
}
return changes;
}
public void SetDefaults()
return changes;
}
private static bool EqualsInheritableData(InheritableData data1, InheritableData data2)
{
if (data1.Value is List<XivChatType>)
return EqualsInheritableDataListXivChatType<XivChatType>(data1, data2);
else
return data1.Value.Equals(data2.Value);
}
private static bool EqualsInheritableDataListXivChatType<TEnum>(InheritableData data1, InheritableData data2)
{
var list1 = data1.Value as List<TEnum>;
var list2 = data2.Value as List<TEnum>;
if (list1 is null || list2 is null || list1.Count != list2.Count)
return false;
for (int i = 0; i < list1.Count; i++)
{
if (m_Defaults != null)
{
// Exclude IsSelected and IsExpanded for UX purposes
SetChanges(m_Defaults.GetAllAsChanges().Where(change => change.Key != nameof(IsSelected) && change.Key != nameof(IsExpanded)));
}
if (!list1[i].Equals(list2[i]))
return false;
}
return true;
}
private static readonly Dictionary<string, string> ObsulteInheritableStringMap = new()
{
{ "IsIconVisibleInChat", nameof(IsRoleIconVisibleInChat) },
{ "IsIconVisibleInNameplate", nameof(IsRoleIconVisibleInNameplates) },
{ "IsIconVisibleInNameplates", nameof(IsRoleIconVisibleInNameplates) }
};
private static string FixObsuleteInheritableStringName(string name)
{
if (ObsulteInheritableStringMap.ContainsKey(name))
return ObsulteInheritableStringMap[name];
else
return name;
}
public void SetChanges(IEnumerable<KeyValuePair<string, InheritableData>> changes)
{
foreach ((var name, var inheritableData) in changes)
{
var namefixed = FixObsuleteInheritableStringName(name);
Inheritables[namefixed].SetData(inheritableData);
}
}
private Dictionary<string, InheritableData> GetAllAsChanges()
{
Dictionary<string, InheritableData> changes = [];
foreach ((var name, var inheritable) in Inheritables)
{
changes[name] = inheritable.GetData();
}
return changes;
}
public void SetDefaults()
{
if (m_Defaults != null)
{
// Exclude IsSelected and IsExpanded for UX purposes
SetChanges(m_Defaults.GetAllAsChanges().Where(change => change.Key != nameof(IsSelected) && change.Key != nameof(IsExpanded)));
}
}
}

View File

@@ -1,9 +1,8 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum TagPosition
{
public enum TagPosition
{
Before,
After,
Replace
}
Before,
After,
Replace
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum TagTarget
{
public enum TagTarget
{
Chat,
Nameplate
}
Chat,
Nameplate
}

View File

@@ -1,41 +1,40 @@
using Lumina.Excel.GeneratedSheets;
using System.Collections.Generic;
namespace PlayerTags.Data
{
public static class WorldHelper
{
private static Dictionary<uint, string>? s_WorldNames = null;
public static Dictionary<uint, string> WorldNames
{
get
{
if (s_WorldNames == null)
{
s_WorldNames = new Dictionary<uint, string>();
namespace PlayerTags.Data;
var worlds = PluginServices.DataManager.GetExcelSheet<World>();
if (worlds != null)
public static class WorldHelper
{
private static Dictionary<uint, string>? s_WorldNames = null;
public static Dictionary<uint, string> WorldNames
{
get
{
if (s_WorldNames == null)
{
s_WorldNames = [];
var worlds = PluginServices.DataManager.GetExcelSheet<World>();
if (worlds != null)
{
foreach (var world in worlds)
{
foreach (var world in worlds)
{
s_WorldNames[world.RowId] = world.Name;
}
s_WorldNames[world.RowId] = world.Name;
}
}
return s_WorldNames;
}
}
public static string? GetWorldName(uint? worldId)
{
if (worldId != null && WorldNames.TryGetValue(worldId.Value, out var name))
{
return name;
}
return null;
return s_WorldNames;
}
}
public static string? GetWorldName(uint? worldId)
{
if (worldId != null && WorldNames.TryGetValue(worldId.Value, out var name))
{
return name;
}
return null;
}
}

View File

@@ -1,40 +1,35 @@
using Dalamud.Game.Text.SeStringHandling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags
namespace PlayerTags;
internal static class Extensions
{
internal static class Extensions
/// <summary>
/// Removes a Payload from a given SeString.
/// Using <code>SeString.Payloads.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this SeString seString, Payload payload)
{
/// <summary>
/// Removes a Payload from a given SeString.
/// Using <code>SeString.Payloads.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this SeString seString, Payload payload)
{
Remove(seString.Payloads, payload);
}
Remove(seString.Payloads, payload);
}
/// <summary>
/// Removes a Payload from a given list.
/// Using <code>List.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this List<Payload> payloads, Payload payload)
/// <summary>
/// Removes a Payload from a given list.
/// Using <code>List.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this List<Payload> payloads, Payload payload)
{
for (int i = 0; i < payloads.Count; i++)
{
for (int i = 0; i < payloads.Count; i++)
if (ReferenceEquals(payloads[i], payload))
{
if (ReferenceEquals(payloads[i], payload))
{
payloads.RemoveAt(i);
break;
}
payloads.RemoveAt(i);
break;
}
}
}

View File

@@ -3,441 +3,487 @@ using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Lumina.Excel.GeneratedSheets;
using Pilz.Dalamud.Tools.Strings;
using PlayerTags.Configuration;
using PlayerTags.Configuration.GameConfig;
using PlayerTags.Data;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Action = System.Action;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// A feature that adds tags to chat messages.
/// </summary>
public class ChatTagTargetFeature : TagTargetFeature
{
/// <summary>
/// A feature that adds tags to chat messages.
/// A match found within a string.
/// </summary>
public class ChatTagTargetFeature : TagTargetFeature
private class StringMatch(SeString seString)
{
/// <summary>
/// A match found within a string.
/// The string that the match was found in.
/// </summary>
private class StringMatch
public SeString SeString { get; init; } = seString;
public List<Payload> DisplayTextPayloads { get; init; } = [];
/// <summary>
/// The matching game object if one exists
/// </summary>
public IGameObject? GameObject { get; init; }
/// <summary>
/// A matching player payload if one exists.
/// </summary>
public PlayerPayload? PlayerPayload { get; init; }
public RawPayload LinkTerminatorPayload { get; init; }
public Payload? PlayerNamePayload
{
/// <summary>
/// The string that the match was found in.
/// </summary>
public SeString SeString { get; init; }
public List<Payload> DisplayTextPayloads { get; init; } = new();
/// <summary>
/// The matching game object if one exists
/// </summary>
public GameObject? GameObject { get; init; }
/// <summary>
/// A matching player payload if one exists.
/// </summary>
public PlayerPayload? PlayerPayload { get; init; }
public RawPayload LinkTerminatorPayload { get; init; }
public Payload? PlayerNamePayload
get
{
get
{
Payload textPayload = null;
string textMatch = GetMatchTextInternal();
string textMatchShort = BuildPlayername(textMatch);
Payload textPayload = null;
string textMatch = GetMatchTextInternal();
string textMatchShort = BuildPlayername(textMatch);
textPayload = DisplayTextPayloads.FirstOrDefault(n => n is TextPayload textPayload && (textPayload.Text.Contains(textMatch) || ((!string.IsNullOrEmpty(textMatchShort)) && textPayload.Text.Contains(textMatchShort))));
textPayload ??= PlayerPayload;
textPayload ??= DisplayTextPayloads.FirstOrDefault();
textPayload = DisplayTextPayloads.FirstOrDefault(n => n is TextPayload textPayload && (textPayload.Text.Contains(textMatch) || ((!string.IsNullOrEmpty(textMatchShort)) && textPayload.Text.Contains(textMatchShort))));
textPayload ??= PlayerPayload;
textPayload ??= DisplayTextPayloads.FirstOrDefault();
return textPayload;
}
}
public bool IsLocalPlayer
{
get
{
return GetMatchTextInternal() == PluginServices.ClientState.LocalPlayer.Name.TextValue;
}
}
public StringMatch(SeString seString)
{
SeString = seString;
}
private string GetMatchTextInternal()
{
if (GameObject != null)
return GameObject.Name.TextValue;
else if (PlayerPayload != null)
return PlayerPayload.PlayerName;
else
return SeString.TextValue;
}
/// <summary>
/// Gets the matches text.
/// </summary>
/// <returns>The match text.</returns>
public string GetMatchText()
{
var playerNamePayload = PlayerNamePayload;
if (playerNamePayload is PlayerPayload pp)
return pp.PlayerName;
else if (playerNamePayload is TextPayload tp)
return tp.Text;
else
return SeString.TextValue;
return textPayload;
}
}
private PluginConfiguration m_PluginConfiguration;
private PluginData m_PluginData;
public ChatTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData)
public bool IsLocalPlayer
{
m_PluginConfiguration = pluginConfiguration;
m_PluginData = pluginData;
PluginServices.ChatGui.ChatMessage += Chat_ChatMessage;
get => GetMatchTextInternal() == PluginServices.ClientState.LocalPlayer.Name.TextValue;
}
public override void Dispose()
private string GetMatchTextInternal()
{
PluginServices.ChatGui.ChatMessage -= Chat_ChatMessage;
base.Dispose();
}
private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
{
if (m_PluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType].IsApplyTagsToAllChatMessagesEnabled)
{
AddTagsToChat(sender, type, true);
AddTagsToChat(message, type, false);
}
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInChat.InheritedValue != null)
{
return tag.IsRoleIconVisibleInChat.InheritedValue.Value;
}
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInChat.InheritedValue != null)
{
return tag.IsTextVisibleInChat.InheritedValue.Value;
}
return false;
if (GameObject != null)
return GameObject.Name.TextValue;
else if (PlayerPayload != null)
return PlayerPayload.PlayerName;
else
return SeString.TextValue;
}
/// <summary>
/// Searches the given string for game object matches.
/// Gets the matches text.
/// </summary>
/// <param name="seString">The string to search.</param>
/// <returns>A list of matched game objects.</returns>
private List<StringMatch> GetStringMatches(SeString seString)
/// <returns>The match text.</returns>
public string GetMatchText()
{
List<StringMatch> stringMatches = new();
Stack<PlayerPayload> curPlayerPayload = new();
Stack<List<Payload>> curRefPayloads = new();
var defaultRawPayload = RawPayload.LinkTerminator.Data;
var playerNamePayload = PlayerNamePayload;
if (playerNamePayload is PlayerPayload pp)
return pp.PlayerName;
else if (playerNamePayload is TextPayload tp)
return tp.Text;
else
return SeString.TextValue;
}
}
foreach (var payload in seString.Payloads)
public ChatTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
{
PluginServices.ChatGui.ChatMessage += ChatGui_ChatMessage;
}
public override void Dispose()
{
PluginServices.ChatGui.ChatMessage -= ChatGui_ChatMessage;
base.Dispose();
GC.SuppressFinalize(this);
}
private void ChatGui_ChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
{
if (EnableGlobal && pluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType].IsApplyTagsToAllChatMessagesEnabled)
{
AddTagsToChat(sender, type, true);
AddTagsToChat(message, type, false);
}
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInChat.InheritedValue != null)
return tag.IsRoleIconVisibleInChat.InheritedValue.Value;
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInChat.InheritedValue != null)
{
return tag.IsTextVisibleInChat.InheritedValue.Value;
}
return false;
}
/// <summary>
/// Searches the given string for game object matches.
/// </summary>
/// <param name="seString">The string to search.</param>
/// <returns>A list of matched game objects.</returns>
private List<StringMatch> GetStringMatches(SeString seString)
{
List<StringMatch> stringMatches = [];
Stack<PlayerPayload> curPlayerPayload = new();
Stack<List<Payload>> curRefPayloads = new();
var defaultRawPayload = RawPayload.LinkTerminator.Data;
foreach (var payload in seString.Payloads)
{
if (payload is PlayerPayload playerPayload)
{
curPlayerPayload.Push(playerPayload);
curRefPayloads.Push([]);
}
else if (payload is RawPayload rawPayload)
{
if (defaultRawPayload.SequenceEqual(rawPayload.Data))
finishCurrentMatch(rawPayload);
}
else
{
if (curRefPayloads.TryPeek(out List<Payload> result))
result.Add(payload);
}
}
// Finally finish, if not closed by RawPayload
finishCurrentMatch(null);
void finishCurrentMatch(RawPayload linkTerminatorPayload)
{
if (curPlayerPayload.TryPop(out PlayerPayload playerPayload))
{
var gameObject = PluginServices.ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName);
var stringMatch = new StringMatch(seString)
{
GameObject = gameObject,
PlayerPayload = playerPayload,
LinkTerminatorPayload = linkTerminatorPayload,
DisplayTextPayloads = curRefPayloads.Pop()
};
stringMatches.Add(stringMatch);
}
}
return stringMatches;
}
private void SplitOffPartyNumberPrefix(SeString sender, XivChatType type)
{
if (type == XivChatType.Party || type == XivChatType.Alliance)
{
PlayerPayload lastPlayerPayload = null;
foreach (var payload in sender.Payloads.ToArray())
{
if (payload is PlayerPayload playerPayload)
lastPlayerPayload = playerPayload;
else if (payload is TextPayload playerNamePayload && lastPlayerPayload != null)
{
curPlayerPayload.Push(playerPayload);
curRefPayloads.Push(new List<Payload>());
}
else if (payload is RawPayload rawPayload)
{
if (defaultRawPayload.SequenceEqual(rawPayload.Data))
finishCurrentMatch(rawPayload);
}
else
{
if (curRefPayloads.TryPeek(out List<Payload> result))
result.Add(payload);
}
}
// Get position of player name in payload
var indexOfPlayerName = playerNamePayload.Text.IndexOf(BuildPlayername(lastPlayerPayload.PlayerName));
// Finally finish, if not closed by RawPayload
finishCurrentMatch(null);
if (indexOfPlayerName == -1)
indexOfPlayerName = playerNamePayload.Text.IndexOf(lastPlayerPayload.PlayerName);
void finishCurrentMatch(RawPayload linkTerminatorPayload)
{
if (curPlayerPayload.TryPop(out PlayerPayload playerPayload))
{
var gameObject = PluginServices.ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName);
var stringMatch = new StringMatch(seString)
if (indexOfPlayerName > 0)
{
GameObject = gameObject,
PlayerPayload = playerPayload,
LinkTerminatorPayload = linkTerminatorPayload,
DisplayTextPayloads = curRefPayloads.Pop()
};
stringMatches.Add(stringMatch);
}
}
// Split off the name from the prefix number
var prefixPayload = new TextPayload(playerNamePayload.Text[..indexOfPlayerName]);
playerNamePayload.Text = playerNamePayload.Text[indexOfPlayerName..];
return stringMatches;
}
private void SplitOffPartyNumberPrefix(SeString sender, XivChatType type)
{
if (type == XivChatType.Party || type == XivChatType.Alliance)
{
PlayerPayload lastPlayerPayload = null;
foreach (var payload in sender.Payloads.ToArray())
{
if (payload is PlayerPayload playerPayload)
lastPlayerPayload = playerPayload;
else if (payload is TextPayload playerNamePayload && lastPlayerPayload != null)
{
// Get position of player name in payload
var indexOfPlayerName = playerNamePayload.Text.IndexOf(BuildPlayername(lastPlayerPayload.PlayerName));
if (indexOfPlayerName == -1)
indexOfPlayerName = playerNamePayload.Text.IndexOf(lastPlayerPayload.PlayerName);
if (indexOfPlayerName > 0)
{
// Split off the name from the prefix number
var prefixPayload = new TextPayload(playerNamePayload.Text[..indexOfPlayerName]);
playerNamePayload.Text = playerNamePayload.Text[indexOfPlayerName..];
// Add prefix number before the player name payload
var playerNamePayloadIndex = sender.Payloads.IndexOf(playerNamePayload);
sender.Payloads.Insert(playerNamePayloadIndex, prefixPayload);
}
// Add prefix number before the player name payload
var playerNamePayloadIndex = sender.Payloads.IndexOf(playerNamePayload);
sender.Payloads.Insert(playerNamePayloadIndex, prefixPayload);
}
}
}
}
private void ParsePayloadsForOwnPlayer(SeString seString, XivChatType chatType, bool isSender)
{
if (PluginServices.ClientState.LocalPlayer != null)
{
foreach (var payload in seString.Payloads.ToArray())
{
if (payload is TextPayload textPayload)
{
List<TextPayload> playerTextPayloads = new List<TextPayload>();
var playerName = PluginServices.ClientState.LocalPlayer.Name.TextValue;
var playerNameShorted = BuildPlayername(playerName);
if (textPayload.Text == playerName || textPayload.Text == playerNameShorted)
{
playerTextPayloads.Add(textPayload);
}
else
{
var usedPlayerName = chatType == XivChatType.Party || chatType == XivChatType.Alliance ? playerNameShorted : playerName;
var textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
while (textMatchIndex >= 0)
{
var textPayloadIndex = seString.Payloads.IndexOf(payload);
// Chop text to the left and insert it as a new payload
if (textMatchIndex > 0)
{
// Add the content before the player
seString.Payloads.Insert(textPayloadIndex++, new TextPayload(textPayload.Text.Substring(0, textMatchIndex)));
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(textMatchIndex, textPayload.Text.Length - textMatchIndex);
}
// This is the last reference to the local player in this payload
if (textPayload.Text.Length == usedPlayerName.Length)
{
playerTextPayloads.Add(textPayload);
break;
}
// Create the new name payload and add it
var playerTextPayload = new TextPayload(usedPlayerName);
playerTextPayloads.Add(playerTextPayload);
seString.Payloads.Insert(textPayloadIndex, playerTextPayload);
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(usedPlayerName.Length);
textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
}
}
foreach (var playerTextPayload in playerTextPayloads)
{
// Fix displaying of abbreviated own player name as the game does this after the chat message handler
playerTextPayload.Text = BuildPlayername(playerTextPayload.Text);
var playerPayload = new PlayerPayload(playerName, PluginServices.ClientState.LocalPlayer.HomeWorld.Id);
int playerPayloadIndex = seString.Payloads.IndexOf(playerTextPayload);
var hasNumberPrefix = isSender && (chatType == XivChatType.Party || chatType == XivChatType.Alliance);
// Ensure to include the group number prefix within the player link
if (hasNumberPrefix)
playerPayloadIndex--;
// Add the Player Link Payload
seString.Payloads.Insert(playerPayloadIndex++, playerPayload);
// Same as above, but reverse
if (hasNumberPrefix)
playerPayloadIndex++;
// Add the Link Terminator to end the Player Link. This should be done behind the Text Payload (display text).
// Normally used to end PlayerPayload linking. But for the own player it has no affect. Anyway, use it, just because. Maybe it's needed in the future somewhere else.
seString.Payloads.Insert(++playerPayloadIndex, RawPayload.LinkTerminator);
// I M P O R T A N T N O T I C E:
// The PlayerPayload is now just temporary. We keep the TextPayload.
// The PayerPayload gets removed at the ChatTagTargetFeature at the end and the TextPayload will be keeped there.
}
}
}
}
}
/// <summary>
/// Adds all configured tags to a chat message.
/// </summary>
/// <param name="message">The message to change.</param>
private void AddTagsToChat(SeString message, XivChatType chatType, bool isSender)
{
// Parse Payloads for local player to be able to work with in the following code
ParsePayloadsForOwnPlayer(message, chatType, isSender);
// Split out the party/alliance number from the PlayerPayload
if (isSender)
SplitOffPartyNumberPrefix(message, chatType);
var stringMatches = GetStringMatches(message);
foreach (var stringMatch in stringMatches)
{
StringChanges stringChanges = new();
bool isTagEnabled(Tag tag)
=> tag.TagPositionInChat.InheritedValue != null && tag.TargetChatTypes.InheritedValue != null &&
(tag.TargetChatTypes.InheritedValue.Contains(chatType) || (!Enum.IsDefined(chatType) && (tag.TargetChatTypesIncludeUndefined?.InheritedValue ?? false)));
if (stringMatch.GameObject is PlayerCharacter playerCharacter)
{
// Add the job tag
if (playerCharacter.ClassJob.GameData != null && m_PluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
{
if (isTagEnabled(jobTag))
{
var payloads = GetPayloads(jobTag, stringMatch.GameObject);
if (payloads.Any())
{
var insertBehindNumberPrefix = jobTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(jobTag, payloads);
}
}
}
// Add randomly generated name tag payload
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var playerName = stringMatch.GetMatchText();
if (playerName != null)
{
var generatedName = BuildPlayername(RandomNameGenerator.Generate(playerName));
if (generatedName != null)
{
AddPayloadChanges(StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges, false);
}
}
}
}
// Add custom tags
if (stringMatch.PlayerPayload != null)
{
Identity identity = m_PluginData.GetIdentity(stringMatch.PlayerPayload);
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
{
if (isTagEnabled(customTag))
{
var customTagPayloads = GetPayloads(customTag, stringMatch.GameObject);
if (customTagPayloads.Any())
{
var insertBehindNumberPrefix = customTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(customTag, customTagPayloads);
}
}
}
}
}
void addPayloadChanges(Tag tag, IEnumerable<Payload> payloads)
{
var insertBehindNumberPrefix = tag.InsertBehindNumberPrefixInChat?.Value ?? true;
var insertPositionInChat = tag.TagPositionInChat.InheritedValue.Value;
AddPayloadChanges((StringPosition)insertPositionInChat, payloads, stringChanges, insertBehindNumberPrefix);
}
// An additional step to apply text color to additional locations
if (stringMatch.PlayerPayload != null && stringMatch.DisplayTextPayloads.Any())
{
Identity identity = m_PluginData.GetIdentity(stringMatch.PlayerPayload);
if (stringMatch.GameObject is PlayerCharacter playerCharacter1)
{
if (playerCharacter1.ClassJob.GameData != null && m_PluginData.JobTags.TryGetValue(playerCharacter1.ClassJob.GameData.Abbreviation, out var jobTag) && isTagEnabled(jobTag))
applyTextFormatting(jobTag);
}
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null && isTagEnabled(customTag))
applyTextFormatting(customTag);
}
void applyTextFormatting(Tag tag)
=> ApplyTextFormatting(stringMatch.GameObject, tag, new[] { message }, new[] { tag.IsTextColorAppliedToChatName }, stringMatch.DisplayTextPayloads);
}
// Finally apply the all the changes to the message
ApplyStringChanges(message, stringChanges, stringMatch.DisplayTextPayloads, stringMatch.PlayerNamePayload);
// Remove PlayerPayload and LinkTerminator if it's your own character (they just got added temporary)
if (stringMatch.IsLocalPlayer)
{
if (stringMatch.PlayerPayload != null)
message.Remove(stringMatch.PlayerPayload);
if (stringMatch.LinkTerminatorPayload != null)
message.Remove(stringMatch.LinkTerminatorPayload);
}
}
}
}
private void ParsePayloadsForOwnPlayer(SeString seString, XivChatType chatType, bool isSender)
{
if (PluginServices.ClientState.LocalPlayer != null)
{
foreach (var payload in seString.Payloads.ToArray())
{
if (payload is TextPayload textPayload)
{
List<TextPayload> playerTextPayloads = [];
var playerName = PluginServices.ClientState.LocalPlayer.Name.TextValue;
var playerNameShorted = BuildPlayername(playerName);
if (textPayload.Text == playerName || textPayload.Text == playerNameShorted)
{
playerTextPayloads.Add(textPayload);
}
else
{
var usedPlayerName = chatType == XivChatType.Party || chatType == XivChatType.Alliance ? playerNameShorted : playerName;
var textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
while (textMatchIndex >= 0)
{
var textPayloadIndex = seString.Payloads.IndexOf(payload);
// Chop text to the left and insert it as a new payload
if (textMatchIndex > 0)
{
// Add the content before the player
seString.Payloads.Insert(textPayloadIndex++, new TextPayload(textPayload.Text.Substring(0, textMatchIndex)));
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(textMatchIndex, textPayload.Text.Length - textMatchIndex);
}
// This is the last reference to the local player in this payload
if (textPayload.Text.Length == usedPlayerName.Length)
{
playerTextPayloads.Add(textPayload);
break;
}
// Create the new name payload and add it
var playerTextPayload = new TextPayload(usedPlayerName);
playerTextPayloads.Add(playerTextPayload);
seString.Payloads.Insert(textPayloadIndex, playerTextPayload);
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(usedPlayerName.Length);
textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
}
}
foreach (var playerTextPayload in playerTextPayloads)
{
// Fix displaying of abbreviated own player name as the game does this after the chat message handler
playerTextPayload.Text = BuildPlayername(playerTextPayload.Text);
var playerPayload = new PlayerPayload(playerName, PluginServices.ClientState.LocalPlayer.HomeWorld.Id);
int playerPayloadIndex = seString.Payloads.IndexOf(playerTextPayload);
var hasNumberPrefix = isSender && (chatType == XivChatType.Party || chatType == XivChatType.Alliance);
// Ensure to include the group number prefix within the player link
if (hasNumberPrefix)
playerPayloadIndex--;
// Add the Player Link Payload
seString.Payloads.Insert(playerPayloadIndex++, playerPayload);
// Same as above, but reverse
if (hasNumberPrefix)
playerPayloadIndex++;
// Add the Link Terminator to end the Player Link. This should be done behind the Text Payload (display text).
// Normally used to end PlayerPayload linking. But for the own player it has no affect. Anyway, use it, just because. Maybe it's needed in the future somewhere else.
seString.Payloads.Insert(++playerPayloadIndex, RawPayload.LinkTerminator);
// I M P O R T A N T N O T I C E:
// The PlayerPayload is now just temporary. We keep the TextPayload.
// The PayerPayload gets removed at the ChatTagTargetFeature at the end and the TextPayload will be keeped there.
}
}
}
}
}
/// <summary>
/// Adds all configured tags to a chat message.
/// </summary>
/// <param name="message">The message to change.</param>
private void AddTagsToChat(SeString message, XivChatType chatType, bool isSender)
{
// Parse Payloads for local player to be able to work with in the following code
ParsePayloadsForOwnPlayer(message, chatType, isSender);
// Split out the party/alliance number from the PlayerPayload
if (isSender)
SplitOffPartyNumberPrefix(message, chatType);
var stringMatches = GetStringMatches(message);
foreach (var stringMatch in stringMatches)
{
StringChanges stringChanges = new();
bool isTagEnabled(Tag tag)
=> tag.TagPositionInChat.InheritedValue != null && tag.TargetChatTypes.InheritedValue != null &&
(tag.TargetChatTypes.InheritedValue.Contains(chatType) || (!Enum.IsDefined(chatType) && (tag.TargetChatTypesIncludeUndefined?.InheritedValue ?? false)));
if (stringMatch.GameObject is IPlayerCharacter playerCharacter)
{
// Add the job tag
if (playerCharacter.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
{
if (isTagEnabled(jobTag))
{
var payloads = GetPayloads(jobTag, playerCharacter);
if (payloads.Any())
{
var insertBehindNumberPrefix = jobTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(jobTag, payloads);
}
}
}
// Add randomly generated name tag payload
if (pluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var playerName = stringMatch.GetMatchText();
if (playerName != null)
{
var generatedName = BuildPlayername(RandomNameGenerator.Generate(playerName));
if (generatedName != null)
{
AddPayloadChanges(StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges, false);
}
}
}
}
// Add custom tags
if (stringMatch.PlayerPayload != null)
{
Identity identity = pluginData.GetIdentity(stringMatch.PlayerPayload);
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
{
if (isTagEnabled(customTag))
{
var customTagPayloads = GetPayloads(customTag, stringMatch.GameObject);
if (customTagPayloads.Any())
{
var insertBehindNumberPrefix = customTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(customTag, customTagPayloads);
}
}
}
}
}
void addPayloadChanges(Tag tag, IEnumerable<Payload> payloads)
{
var insertBehindNumberPrefix = tag.InsertBehindNumberPrefixInChat?.Value ?? true;
var insertPositionInChat = tag.TagPositionInChat.InheritedValue.Value;
AddPayloadChanges((StringPosition)insertPositionInChat, payloads, stringChanges, insertBehindNumberPrefix);
}
// An additional step to apply text color to additional locations
if (stringMatch.PlayerPayload != null && stringMatch.DisplayTextPayloads.Any())
{
Identity identity = pluginData.GetIdentity(stringMatch.PlayerPayload);
if (stringMatch.GameObject is IPlayerCharacter playerCharacter1)
{
if (playerCharacter1.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(playerCharacter1.ClassJob.GameData.Abbreviation, out var jobTag) && isTagEnabled(jobTag))
applyTextFormatting(jobTag);
}
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null && isTagEnabled(customTag))
applyTextFormatting(customTag);
}
void applyTextFormatting(Tag tag)
=> ApplyTextFormatting(stringMatch.GameObject, tag, new[] { message }, new[] { tag.IsTextColorAppliedToChatName }, stringMatch.DisplayTextPayloads);
}
// Finally apply the all the changes to the message
ApplyStringChanges(message, stringChanges, stringMatch.DisplayTextPayloads, stringMatch.PlayerNamePayload);
// Remove PlayerPayload and LinkTerminator if it's your own character (they just got added temporary)
if (stringMatch.IsLocalPlayer)
{
if (stringMatch.PlayerPayload != null)
message.Remove(stringMatch.PlayerPayload);
if (stringMatch.LinkTerminatorPayload != null)
message.Remove(stringMatch.LinkTerminatorPayload);
}
}
}
private void ApplyTextFormatting(IGameObject? gameObject, Tag tag, SeString[] destStrings, InheritableValue<bool>[] textColorApplied, List<Payload> preferedPayloads, ushort? overwriteTextColor = null)
{
if (IsTagVisible(tag, gameObject))
{
for (int i = 0; i < destStrings.Length; i++)
{
var destString = destStrings[i];
var isTextColorApplied = textColorApplied[i];
applyTextColor(destString, isTextColorApplied, tag.TextColor);
}
}
void applyTextColor(SeString destPayload, InheritableValue<bool> enableFlag, InheritableValue<ushort> colorValue)
{
var colorToUse = overwriteTextColor ?? colorValue?.InheritedValue;
if (shouldApplyFormattingPayloads(destPayload)
&& enableFlag.InheritedValue != null
&& enableFlag.InheritedValue.Value
&& colorToUse != null)
applyTextFormattingPayloads(destPayload, new UIForegroundPayload(colorToUse.Value), new UIForegroundPayload(0));
}
bool shouldApplyFormattingPayloads(SeString destPayload)
=> destPayload.Payloads.Any(payload => payload is TextPayload || payload is PlayerPayload);
void applyTextFormattingPayloads(SeString destPayload, Payload startPayload, Payload endPayload)
{
if (preferedPayloads == null || !preferedPayloads.Any())
applyTextFormattingPayloadToStartAndEnd(destPayload, startPayload, endPayload);
else
applyTextFormattingPayloadsToSpecificPosition(destPayload, startPayload, endPayload, preferedPayloads);
}
void applyTextFormattingPayloadToStartAndEnd(SeString destPayload, Payload startPayload, Payload endPayload)
{
destPayload.Payloads.Insert(0, startPayload);
destPayload.Payloads.Add(endPayload);
}
void applyTextFormattingPayloadsToSpecificPosition(SeString destPayload, Payload startPayload, Payload endPayload, List<Payload> preferedPayload)
{
int payloadStartIndex = destPayload.Payloads.IndexOf(preferedPayloads.First());
destPayload.Payloads.Insert(payloadStartIndex, startPayload);
int payloadEndIndex = destPayload.Payloads.IndexOf(preferedPayloads.Last());
destPayload.Payloads.Insert(payloadEndIndex + 1, endPayload);
}
}
/// <summary>
/// Applies changes to the given string.
/// </summary>
/// <param name="seString">The string to apply changes to.</param>
/// <param name="stringChanges">The changes to apply.</param>
/// <param name="anchorPayload">The payload in the string that changes should be anchored to. If there is no anchor, the changes will be applied to the entire string.</param>
protected void ApplyStringChanges(SeString seString, StringChanges stringChanges, List<Payload> anchorPayloads = null, Payload anchorReplacePayload = null)
{
var props = new StringChangesProps
{
Destination = seString,
AnchorPayload = anchorReplacePayload,
AnchorPayloads = anchorPayloads,
StringChanges = stringChanges
};
StringUpdateFactory.ApplyStringChanges(props);
}
}

View File

@@ -1,6 +1,4 @@
using Dalamud.ContextMenu;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Logging;
using Dalamud.Game.Gui.ContextMenu;
using PlayerTags.Configuration;
using PlayerTags.Data;
using PlayerTags.Resources;
@@ -8,93 +6,81 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// A feature that adds options for the management of custom tags to context menus.
/// </summary>
public class CustomTagsContextMenuFeature : FeatureBase, IDisposable
{
/// <summary>
/// A feature that adds options for the management of custom tags to context menus.
/// </summary>
public class CustomTagsContextMenuFeature : IDisposable
private readonly string[] supportedAddonNames =
[
null,
"_PartyList",
"ChatLog",
"ContactList",
"ContentMemberList",
"CrossWorldLinkshell",
"FreeCompany",
"FriendList",
"LookingForGroup",
"LinkShell",
"PartyMemberList",
"SocialList",
];
public CustomTagsContextMenuFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
{
private string?[] SupportedAddonNames = new string?[]
PluginServices.ContextMenu.OnMenuOpened += ContextMenu_OnMenuOpened; ;
}
public void Dispose()
{
PluginServices.ContextMenu.OnMenuOpened -= ContextMenu_OnMenuOpened;
}
private void ContextMenu_OnMenuOpened(IMenuOpenedArgs args)
{
if (!EnableGlobal || !pluginConfiguration.IsCustomTagsContextMenuEnabled
|| args.MenuType != ContextMenuType.Default
|| args.Target is not MenuTargetDefault menuTarget
|| !supportedAddonNames.Contains(args.AddonName))
return;
Identity? identity = pluginData.GetIdentity(menuTarget);
if (identity != null)
{
null,
"_PartyList",
"ChatLog",
"ContactList",
"ContentMemberList",
"CrossWorldLinkshell",
"FreeCompany",
"FriendList",
"LookingForGroup",
"LinkShell",
"PartyMemberList",
"SocialList",
};
private PluginConfiguration m_PluginConfiguration;
private PluginData m_PluginData;
private DalamudContextMenu? m_ContextMenu;
public CustomTagsContextMenuFeature(PluginConfiguration pluginConfiguration, PluginData pluginData)
{
m_PluginConfiguration = pluginConfiguration;
m_PluginData = pluginData;
m_ContextMenu = new DalamudContextMenu();
m_ContextMenu.OnOpenGameObjectContextMenu += ContextMenuHooks_ContextMenuOpened;
}
public void Dispose()
{
if (m_ContextMenu != null)
var allTags = new Dictionary<Tag, bool>();
foreach (var customTag in pluginData.CustomTags)
{
m_ContextMenu.OnOpenGameObjectContextMenu -= ContextMenuHooks_ContextMenuOpened;
((IDisposable)m_ContextMenu).Dispose();
m_ContextMenu = null;
}
}
private void ContextMenuHooks_ContextMenuOpened(GameObjectContextMenuOpenArgs contextMenuOpenedArgs)
{
if (!m_PluginConfiguration.IsCustomTagsContextMenuEnabled
|| !SupportedAddonNames.Contains(contextMenuOpenedArgs.ParentAddonName))
{
return;
var isAdded = identity.CustomTagIds.Contains(customTag.CustomId.Value);
allTags.Add(customTag, isAdded);
}
Identity? identity = m_PluginData.GetIdentity(contextMenuOpenedArgs);
if (identity != null)
var sortedTags = allTags.OrderBy(n => n.Value);
foreach (var tag in sortedTags)
{
var allTags = new Dictionary<Tag, bool>();
foreach (var customTag in m_PluginData.CustomTags)
{
var isAdded = identity.CustomTagIds.Contains(customTag.CustomId.Value);
allTags.Add(customTag, isAdded);
}
var sortedTags = allTags.OrderBy(n => n.Value);
foreach (var tag in sortedTags)
{
string menuItemText;
if (tag.Value)
menuItemText = Strings.Loc_Static_ContextMenu_RemoveTag;
else
menuItemText = Strings.Loc_Static_ContextMenu_AddTag;
menuItemText = string.Format(menuItemText, tag.Key.Text.Value);
string menuItemText;
if (tag.Value)
menuItemText = Strings.Loc_Static_ContextMenu_RemoveTag;
else
menuItemText = Strings.Loc_Static_ContextMenu_AddTag;
menuItemText = string.Format(menuItemText, tag.Key.Text.Value);
contextMenuOpenedArgs.AddCustomItem(
new GameObjectContextMenuItem(menuItemText, openedEventArgs =>
{
if (tag.Value)
m_PluginData.RemoveCustomTagFromIdentity(tag.Key, identity);
else
m_PluginData.AddCustomTagToIdentity(tag.Key, identity);
m_PluginConfiguration.Save(m_PluginData);
})
{
IsSubMenu = false
});
}
args.AddMenuItem(new()
{
IsSubmenu = false,
IsEnabled = true,
Name = menuItemText,
OnClicked = openedEventArgs =>
{
if (tag.Value)
pluginData.RemoveCustomTagFromIdentity(tag.Key, identity);
else
pluginData.AddCustomTagToIdentity(tag.Key, identity);
pluginConfiguration.Save(pluginData);
},
});
}
}
}

View File

@@ -0,0 +1,18 @@
using PlayerTags.Configuration;
using PlayerTags.Data;
namespace PlayerTags.Features;
public class FeatureBase
{
protected readonly PluginConfiguration pluginConfiguration;
protected readonly PluginData pluginData;
public virtual bool EnableGlobal => pluginConfiguration.EnabledGlobal;
protected FeatureBase(PluginConfiguration pluginConfiguration, PluginData pluginData)
{
this.pluginConfiguration = pluginConfiguration;
this.pluginData = pluginData;
}
}

View File

@@ -1,285 +1,222 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Excel.GeneratedSheets;
using Pilz.Dalamud.Icons;
using Pilz.Dalamud.Nameplates.Tools;
using Pilz.Dalamud.NamePlate;
using Pilz.Dalamud.Tools.NamePlates;
using Pilz.Dalamud.Tools.Strings;
using PlayerTags.Configuration;
using PlayerTags.Data;
using PlayerTags.GameInterface.Nameplates;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// A feature that adds tags to nameplates.
/// </summary>
public class NameplateTagTargetFeature : TagTargetFeature
{
/// <summary>
/// A feature that adds tags to nameplates.
/// </summary>
public class NameplateTagTargetFeature : TagTargetFeature
private readonly StatusIconPriorizer statusiconPriorizer;
private readonly JobIconSets jobIconSets = new();
public NameplateTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
{
private readonly PluginConfiguration m_PluginConfiguration;
private readonly PluginData m_PluginData;
private readonly StatusIconPriorizer statusiconPriorizer;
private readonly JobIconSets jobIconSets = new();
private Nameplate? m_Nameplate;
statusiconPriorizer = new(pluginConfiguration.StatusIconPriorizerSettings);
PluginServices.NamePlateGui.OnNamePlateUpdate += NamePlateGui_OnNamePlateUpdate;
}
public NameplateTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData)
public override void Dispose()
{
PluginServices.NamePlateGui.OnNamePlateUpdate -= NamePlateGui_OnNamePlateUpdate;
base.Dispose();
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInNameplates.InheritedValue != null)
return tag.IsRoleIconVisibleInNameplates.InheritedValue.Value;
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInNameplates.InheritedValue != null)
return tag.IsTextVisibleInNameplates.InheritedValue.Value;
return false;
}
private void NamePlateGui_OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
{
if (!EnableGlobal)
return;
foreach (var handler in handlers)
{
m_PluginConfiguration = pluginConfiguration;
m_PluginData = pluginData;
statusiconPriorizer = new(pluginConfiguration.StatusIconPriorizerSettings);
// Only handle player nameplates
if (handler.NamePlateKind != NamePlateKind.PlayerCharacter || handler.PlayerCharacter == null)
continue;
PluginServices.ClientState.Login += ClientState_Login;
PluginServices.ClientState.Logout += ClientState_Logout;
var beforeTitleBytes = handler.InfoView.Title.Encode();
var generalOptions = pluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType];
Hook();
}
public override void Dispose()
{
Unhook();
PluginServices.ClientState.Logout -= ClientState_Logout;
PluginServices.ClientState.Login -= ClientState_Login;
base.Dispose();
}
private void Hook()
{
if (m_Nameplate == null)
{
m_Nameplate = new Nameplate();
if (!m_Nameplate.IsValid)
{
m_Nameplate = null;
}
if (m_Nameplate != null)
{
m_Nameplate.PlayerNameplateUpdated += Nameplate_PlayerNameplateUpdated;
}
}
}
private void Unhook()
{
if (m_Nameplate != null)
{
m_Nameplate.PlayerNameplateUpdated -= Nameplate_PlayerNameplateUpdated;
m_Nameplate.Dispose();
m_Nameplate = null;
}
}
private void ClientState_Login(object? sender, EventArgs e)
{
Hook();
}
private void ClientState_Logout(object? sender, EventArgs e)
{
Unhook();
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInNameplates.InheritedValue != null)
{
return tag.IsRoleIconVisibleInNameplates.InheritedValue.Value;
}
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInNameplates.InheritedValue != null)
{
return tag.IsTextVisibleInNameplates.InheritedValue.Value;
}
return false;
}
private unsafe void Nameplate_PlayerNameplateUpdated(PlayerNameplateUpdatedArgs args)
{
var beforeTitleBytes = args.Title.Encode();
var iconID = args.IconId;
var generalOptions = m_PluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType];
AddTagsToNameplate(args.PlayerCharacter, args.Name, args.Title, args.FreeCompany, ref iconID, generalOptions);
args.IconId = iconID;
AddTagsToNameplate(handler, generalOptions);
if (generalOptions.NameplateTitlePosition == NameplateTitlePosition.AlwaysAboveName)
args.IsTitleAboveName = true;
handler.IsPrefixTitle = true;
else if (generalOptions.NameplateTitlePosition == NameplateTitlePosition.AlwaysBelowName)
args.IsTitleAboveName = false;
handler.IsPrefixTitle = false;
if (generalOptions.NameplateTitleVisibility == NameplateTitleVisibility.Always)
args.IsTitleVisible = true;
handler.DisplayTitle = true;
else if (generalOptions.NameplateTitleVisibility == NameplateTitleVisibility.Never)
args.IsTitleVisible = false;
handler.DisplayTitle = false;
else if (generalOptions.NameplateTitleVisibility == NameplateTitleVisibility.WhenHasTags)
{
bool hasTitleChanged = !beforeTitleBytes.SequenceEqual(args.Title.Encode());
args.IsTitleVisible = hasTitleChanged;
}
handler.DisplayTitle = !beforeTitleBytes.SequenceEqual(handler.InfoView.Title.Encode());
if (generalOptions.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.Never)
args.FreeCompany.Payloads.Clear();
handler.RemoveFreeCompanyTag();
}
}
/// <summary>
/// Adds the given payload changes to the specified locations.
/// </summary>
/// <param name="nameplateElement">The nameplate element of the changes.</param>
/// <param name="tagPosition">The position of the changes.</param>
/// <param name="payloadChanges">The payload changes to add.</param>
/// <param name="nameplateChanges">The dictionary to add changes to.</param>
private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable<Payload> payloadChanges, NameplateChanges nameplateChanges, bool forceUsingSingleAnchorPayload)
/// <summary>
/// Adds the given payload changes to the specified locations.
/// </summary>
/// <param name="nameplateElement">The nameplate element of the changes.</param>
/// <param name="tagPosition">The position of the changes.</param>
/// <param name="payloadChanges">The payload changes to add.</param>
/// <param name="nameplateChanges">The dictionary to add changes to.</param>
private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable<Payload> payloadChanges, NameplateChanges nameplateChanges, bool forceUsingSingleAnchorPayload)
{
if (payloadChanges.Any())
{
if (payloadChanges.Any())
var changes = nameplateChanges.GetChange((NameplateElements)nameplateElement);
AddPayloadChanges((StringPosition)tagPosition, payloadChanges, changes.Changes, forceUsingSingleAnchorPayload);
}
}
/// <summary>
/// Adds tags to the nameplate of a game object.
/// </summary>
/// <param name="playerCharacter">The game object context.</param>
/// <param name="name">The name text to change.</param>
/// <param name="title">The title text to change.</param>
/// <param name="freeCompany">The free company text to change.</param>
private void AddTagsToNameplate(INamePlateUpdateHandler handler, GeneralOptionsClass generalOptions)
{
int? newStatusIcon = null;
var nameplateChanges = new NameplateChanges(handler);
if (handler.PlayerCharacter != null && (!handler.PlayerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling != DeadPlayerHandling.Ignore))
{
var classJob = handler.PlayerCharacter.ClassJob;
var classJobGameData = classJob?.GameData;
// Add the job tags
if (classJobGameData != null && pluginData.JobTags.TryGetValue(classJobGameData.Abbreviation, out var jobTag))
{
var changes = nameplateChanges.GetChanges((NameplateElements)nameplateElement);
AddPayloadChanges((StringPosition)tagPosition, payloadChanges, changes, forceUsingSingleAnchorPayload);
if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null)
checkTag(jobTag);
}
}
private NameplateChanges GenerateEmptyNameplateChanges(SeString name, SeString title, SeString freeCompany)
{
NameplateChanges nameplateChanges = new();
nameplateChanges.GetProps(NameplateElements.Name).Destination = name;
nameplateChanges.GetProps(NameplateElements.Title).Destination = title;
nameplateChanges.GetProps(NameplateElements.FreeCompany).Destination = freeCompany;
return nameplateChanges;
}
/// <summary>
/// Adds tags to the nameplate of a game object.
/// </summary>
/// <param name="gameObject">The game object context.</param>
/// <param name="name">The name text to change.</param>
/// <param name="title">The title text to change.</param>
/// <param name="freeCompany">The free company text to change.</param>
private void AddTagsToNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, ref int statusIcon, GeneralOptionsClass generalOptions)
{
var playerCharacter = gameObject as PlayerCharacter;
int? newStatusIcon = null;
NameplateChanges nameplateChanges = GenerateEmptyNameplateChanges(name, title, freeCompany);
if (playerCharacter != null && (!playerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling != DeadPlayerHandling.Ignore))
// Add the randomly generated name tag payload
if (pluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var classJob = playerCharacter.ClassJob;
var classJobGameData = classJob?.GameData;
// Add the job tags
if (classJobGameData != null && m_PluginData.JobTags.TryGetValue(classJobGameData.Abbreviation, out var jobTag))
var characterName = handler.PlayerCharacter.Name.TextValue;
if (characterName != null)
{
if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null)
checkTag(jobTag);
}
// Add the randomly generated name tag payload
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var characterName = playerCharacter.Name.TextValue;
if (characterName != null)
{
var generatedName = RandomNameGenerator.Generate(characterName);
if (generatedName != null)
AddPayloadChanges(NameplateElement.Name, TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges, false);
}
}
// Add custom tags
Identity identity = m_PluginData.GetIdentity(playerCharacter);
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
checkTag(customTag);
}
void checkTag(Tag tag)
{
if (tag.TagTargetInNameplates.InheritedValue != null && tag.TagPositionInNameplates.InheritedValue != null)
{
var payloads = GetPayloads(tag, gameObject);
if (payloads.Any())
AddPayloadChanges(tag.TagTargetInNameplates.InheritedValue.Value, tag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges, false);
}
if (IsTagVisible(tag, gameObject) && newStatusIcon == null && classJob != null && (tag.IsJobIconVisibleInNameplates?.InheritedValue ?? false))
newStatusIcon = jobIconSets.GetJobIcon(tag.JobIconSet?.InheritedValue ?? JobIconSetName.Framed, classJob.Id);
var generatedName = RandomNameGenerator.Generate(characterName);
if (generatedName != null)
AddPayloadChanges(NameplateElement.Name, TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges, false);
}
}
// Apply new status icon
if (newStatusIcon != null)
// Add custom tags
Identity identity = pluginData.GetIdentity(handler.PlayerCharacter);
foreach (var customTagId in identity.CustomTagIds)
{
var change = nameplateChanges.GetChange(NameplateElements.Name, StringPosition.Before);
NameplateUpdateFactory.ApplyStatusIconWithPrio(ref statusIcon, (int)newStatusIcon, change, ActivityContextManager.CurrentActivityContext, statusiconPriorizer, m_PluginConfiguration.MoveStatusIconToNameplateTextIfPossible);
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
checkTag(customTag);
}
// Gray out the nameplate
if (playerCharacter != null && playerCharacter.IsDead && generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.GrayOut)
GrayOutNameplate(gameObject, nameplateChanges);
// Build the final strings out of the payloads
ApplyNameplateChanges(nameplateChanges);
if (playerCharacter != null && (!playerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.Include))
void checkTag(Tag tag)
{
// An additional step to apply text color to additional locations
Identity identity = m_PluginData.GetIdentity(playerCharacter);
foreach (var customTagId in identity.CustomTagIds)
if (tag.TagTargetInNameplates.InheritedValue != null && tag.TagPositionInNameplates.InheritedValue != null)
{
var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
applyTextFormatting(customTag);
}
if (playerCharacter.ClassJob.GameData != null && m_PluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
applyTextFormatting(jobTag);
void applyTextFormatting(Tag tag)
{
var destStrings = new[] { name, title, freeCompany };
var isTextColorApplied = new[] { tag.IsTextColorAppliedToNameplateName, tag.IsTextColorAppliedToNameplateTitle, tag.IsTextColorAppliedToNameplateFreeCompany };
ApplyTextFormatting(gameObject, tag, new[] { name, title, freeCompany }, isTextColorApplied, null);
var payloads = GetPayloads(tag, handler.PlayerCharacter);
if (payloads.Length != 0)
AddPayloadChanges(tag.TagTargetInNameplates.InheritedValue.Value, tag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges, false);
}
if (IsTagVisible(tag, handler.PlayerCharacter) && newStatusIcon == null && classJob != null && (tag.IsJobIconVisibleInNameplates?.InheritedValue ?? false))
newStatusIcon = jobIconSets.GetJobIcon(tag.JobIconSet?.InheritedValue ?? JobIconSetName.Framed, classJob.Id);
}
}
private void GrayOutNameplate(GameObject gameObject, NameplateChanges nameplateChanges)
// Apply new status icon
if (newStatusIcon != null)
{
if (gameObject is PlayerCharacter playerCharacter)
{
foreach (NameplateElements element in Enum.GetValues<NameplateElements>())
{
nameplateChanges.GetChange(element, StringPosition.Before).Payloads.Add(new UIForegroundPayload(3));
nameplateChanges.GetChange(element, StringPosition.After).Payloads.Add(new UIForegroundPayload(0));
}
}
NameplateUpdateFactory.ApplyStatusIconWithPrio(handler, (int)newStatusIcon, ActivityContextManager.CurrentActivityContext, statusiconPriorizer, pluginConfiguration.MoveStatusIconToNameplateTextIfPossible);
}
protected void ApplyNameplateChanges(NameplateChanges nameplateChanges)
// Build the final strings out of the payloads
NameplateUpdateFactory.ApplyNameplateChanges(new NameplateChangesProps
{
var props = new NameplateChangesProps
Changes = nameplateChanges
});
// Gray out the nameplate
if (handler.PlayerCharacter != null && handler.PlayerCharacter.IsDead && generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.GrayOut)
GrayOutNameplate(handler.PlayerCharacter, nameplateChanges);
// Apply text color
if (handler.PlayerCharacter != null && (!handler.PlayerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.Include))
{
Identity identity = pluginData.GetIdentity(handler.PlayerCharacter);
foreach (var customTagId in identity.CustomTagIds)
{
Changes = nameplateChanges
};
NameplateUpdateFactory.ApplyNameplateChanges(props);
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
applyTextFormatting(customTag);
}
if (handler.PlayerCharacter.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(handler.PlayerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
applyTextFormatting(jobTag);
void applyTextFormatting(Tag tag)
{
var dic = new Dictionary<NameplateElementChange, InheritableValue<bool>>
{
{ nameplateChanges.GetChange(NameplateElements.Name), tag.IsTextColorAppliedToNameplateName },
{ nameplateChanges.GetChange(NameplateElements.Title), tag.IsTextColorAppliedToNameplateTitle },
{ nameplateChanges.GetChange(NameplateElements.FreeCompany), tag.IsTextColorAppliedToNameplateFreeCompany },
};
ApplyTextFormatting(handler.PlayerCharacter, tag, dic, null);
}
}
}
private void GrayOutNameplate(IPlayerCharacter playerCharacter, NameplateChanges nameplateChanges)
{
foreach (var element in Enum.GetValues<NameplateElements>())
nameplateChanges.GetChange(element).ApplyFormatting(new SeString().Append(new UIForegroundPayload(3)), new SeString().Append(new UIForegroundPayload(0)));
}
protected void ApplyTextFormatting(IPlayerCharacter? gameObject, Tag tag, IEnumerable<KeyValuePair<NameplateElementChange, InheritableValue<bool>>> changes, ushort? overwriteTextColor = null)
{
if (IsTagVisible(tag, gameObject))
{
foreach (var kvp in changes)
{
var change = kvp.Key;
var enableFlag = kvp.Value;
if (enableFlag.InheritedValue != null && enableFlag.InheritedValue.Value && (overwriteTextColor ?? tag.TextColor?.InheritedValue) is ushort colorToUse)
change.ApplyFormatting(new SeString().Append(new UIForegroundPayload(colorToUse)), new SeString().Append(new UIForegroundPayload(0)));
}
}
}
}

View File

@@ -2,293 +2,171 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Lumina.Excel.GeneratedSheets;
using Pilz.Dalamud.ActivityContexts;
using Pilz.Dalamud.Tools.Strings;
using PlayerTags.Configuration;
using PlayerTags.Configuration.GameConfig;
using PlayerTags.Data;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using GameObject = Dalamud.Game.ClientState.Objects.Types.GameObject;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// The base of a feature that adds tags to UI elements.
/// </summary>
public abstract class TagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : FeatureBase(pluginConfiguration, pluginData), IDisposable
{
/// <summary>
/// The base of a feature that adds tags to UI elements.
/// </summary>
public abstract class TagTargetFeature : IDisposable
public ActivityContextManager ActivityContextManager { get; init; } = new();
public virtual void Dispose()
{
public ActivityContextManager ActivityContextManager { get; init; }
ActivityContextManager.Dispose();
GC.SuppressFinalize(this);
}
public TagTargetFeature()
protected abstract bool IsIconVisible(Tag tag);
protected abstract bool IsTextVisible(Tag tag);
protected bool IsTagVisible(Tag tag, IGameObject? gameObject)
{
bool isVisibleForActivity = ActivityContextHelper.GetIsVisible(ActivityContextManager.CurrentActivityContext.ActivityType,
tag.IsVisibleInPveDuties.InheritedValue ?? false,
tag.IsVisibleInPvpDuties.InheritedValue ?? false,
tag.IsVisibleInOverworld.InheritedValue ?? false);
if (!isVisibleForActivity)
return false;
if (gameObject is IPlayerCharacter playerCharacter)
{
ActivityContextManager = new();
}
bool isVisibleForPlayer = PlayerContextHelper.GetIsVisible(playerCharacter,
tag.IsVisibleForSelf.InheritedValue ?? false,
tag.IsVisibleForFriendPlayers.InheritedValue ?? false,
tag.IsVisibleForPartyPlayers.InheritedValue ?? false,
tag.IsVisibleForAlliancePlayers.InheritedValue ?? false,
tag.IsVisibleForEnemyPlayers.InheritedValue ?? false,
tag.IsVisibleForOtherPlayers.InheritedValue ?? false);
public virtual void Dispose()
{
ActivityContextManager.Dispose();
}
protected abstract bool IsIconVisible(Tag tag);
protected abstract bool IsTextVisible(Tag tag);
protected bool IsTagVisible(Tag tag, GameObject? gameObject)
{
bool isVisibleForActivity = ActivityContextHelper.GetIsVisible(ActivityContextManager.CurrentActivityContext.ActivityType,
tag.IsVisibleInPveDuties.InheritedValue ?? false,
tag.IsVisibleInPvpDuties.InheritedValue ?? false,
tag.IsVisibleInOverworld.InheritedValue ?? false);
if (!isVisibleForActivity)
{
if (!isVisibleForPlayer)
return false;
}
if (gameObject is PlayerCharacter playerCharacter)
{
bool isVisibleForPlayer = PlayerContextHelper.GetIsVisible(playerCharacter,
tag.IsVisibleForSelf.InheritedValue ?? false,
tag.IsVisibleForFriendPlayers.InheritedValue ?? false,
tag.IsVisibleForPartyPlayers.InheritedValue ?? false,
tag.IsVisibleForAlliancePlayers.InheritedValue ?? false,
tag.IsVisibleForEnemyPlayers.InheritedValue ?? false,
tag.IsVisibleForOtherPlayers.InheritedValue ?? false);
if (!isVisibleForPlayer)
{
return false;
}
}
return true;
}
/// <summary>
/// Gets the payloads for the given tag and game object depending on visibility conditions.
/// </summary>
/// <param name="gameObject">The game object to get payloads for.</param>
/// <param name="tag">The tag config to get payloads for.</param>
/// <returns>A list of payloads for the given tag.</returns>
protected Payload[] GetPayloads(Tag tag, GameObject? gameObject)
{
if (!IsTagVisible(tag, gameObject))
{
return Array.Empty<Payload>();
}
return true;
}
return CreatePayloads(tag);
/// <summary>
/// Gets the payloads for the given tag and game object depending on visibility conditions.
/// </summary>
/// <param name="gameObject">The game object to get payloads for.</param>
/// <param name="tag">The tag config to get payloads for.</param>
/// <returns>A list of payloads for the given tag.</returns>
protected Payload[] GetPayloads(Tag tag, IGameObject? playerCharacter)
{
if (!IsTagVisible(tag, playerCharacter))
return [];
return CreatePayloads(tag);
}
/// <summary>
/// Creates payloads for the given tag.
/// </summary>
/// <param name="tag">The tag to create payloads for.</param>
/// <returns>The payloads for the given tag.</returns>
private Payload[] CreatePayloads(Tag tag)
{
List<Payload> newPayloads = [];
BitmapFontIcon? icon = null;
string? text = null;
if (IsIconVisible(tag))
icon = tag.Icon.InheritedValue;
if (icon != null && icon.Value != BitmapFontIcon.None)
newPayloads.Add(new IconPayload(icon.Value));
if (IsTextVisible(tag))
text = tag.Text.InheritedValue;
if (!string.IsNullOrWhiteSpace(text))
{
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
newPayloads.Add(new EmphasisItalicPayload(true));
if (tag.TextGlowColor.InheritedValue != null)
newPayloads.Add(new UIGlowPayload(tag.TextGlowColor.InheritedValue.Value));
if (tag.TextColor.InheritedValue != null)
newPayloads.Add(new UIForegroundPayload(tag.TextColor.InheritedValue.Value));
newPayloads.Add(new TextPayload(text));
if (tag.TextColor.InheritedValue != null)
newPayloads.Add(new UIForegroundPayload(0));
if (tag.TextGlowColor.InheritedValue != null)
newPayloads.Add(new UIGlowPayload(0));
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
newPayloads.Add(new EmphasisItalicPayload(false));
}
/// <summary>
/// Creates payloads for the given tag.
/// </summary>
/// <param name="tag">The tag to create payloads for.</param>
/// <returns>The payloads for the given tag.</returns>
private Payload[] CreatePayloads(Tag tag)
return [.. newPayloads];
}
protected static string BuildPlayername(string name)
{
var logNameType = GameConfigHelper.Instance.GetLogNameType();
var result = string.Empty;
if (logNameType != null && !string.IsNullOrEmpty(name))
{
List<Payload> newPayloads = new List<Payload>();
var nameSplitted = name.Split(' ');
BitmapFontIcon? icon = null;
if (IsIconVisible(tag))
if (nameSplitted.Length > 1)
{
icon = tag.Icon.InheritedValue;
}
var firstName = nameSplitted[0];
var lastName = nameSplitted[1];
if (icon != null && icon.Value != BitmapFontIcon.None)
{
newPayloads.Add(new IconPayload(icon.Value));
}
string? text = null;
if (IsTextVisible(tag))
{
text = tag.Text.InheritedValue;
}
if (!string.IsNullOrWhiteSpace(text))
{
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
switch (logNameType)
{
newPayloads.Add(new EmphasisItalicPayload(true));
case LogNameType.FullName:
result = $"{firstName} {lastName}";
break;
case LogNameType.LastNameShorted:
result = $"{firstName} {lastName[..1]}.";
break;
case LogNameType.FirstNameShorted:
result = $"{firstName[..1]}. {lastName}";
break;
case LogNameType.Initials:
result = $"{firstName[..1]}. {lastName[..1]}.";
break;
}
if (tag.TextGlowColor.InheritedValue != null)
{
newPayloads.Add(new UIGlowPayload(tag.TextGlowColor.InheritedValue.Value));
}
if (tag.TextColor.InheritedValue != null)
{
newPayloads.Add(new UIForegroundPayload(tag.TextColor.InheritedValue.Value));
}
newPayloads.Add(new TextPayload(text));
if (tag.TextColor.InheritedValue != null)
{
newPayloads.Add(new UIForegroundPayload(0));
}
if (tag.TextGlowColor.InheritedValue != null)
{
newPayloads.Add(new UIGlowPayload(0));
}
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
{
newPayloads.Add(new EmphasisItalicPayload(false));
}
}
return newPayloads.ToArray();
}
protected static string BuildPlayername(string name)
{
var logNameType = GameConfigHelper.Instance.GetLogNameType();
var result = string.Empty;
if (logNameType != null && !string.IsNullOrEmpty(name))
{
var nameSplitted = name.Split(' ');
if (nameSplitted.Length > 1)
{
var firstName = nameSplitted[0];
var lastName = nameSplitted[1];
switch (logNameType)
{
case LogNameType.FullName:
result = $"{firstName} {lastName}";
break;
case LogNameType.LastNameShorted:
result = $"{firstName} {lastName[..1]}.";
break;
case LogNameType.FirstNameShorted:
result = $"{firstName[..1]}. {lastName}";
break;
case LogNameType.Initials:
result = $"{firstName[..1]}. {lastName[..1]}.";
break;
}
}
}
if (string.IsNullOrEmpty(result))
result = name;
return result;
}
/// <summary>
/// Adds the given payload changes to the dictionary.
/// </summary>
/// <param name="tagPosition">The position to add changes to.</param>
/// <param name="payloads">The payloads to add.</param>
/// <param name="stringChanges">The dictionary to add the changes to.</param>
protected void AddPayloadChanges(StringPosition tagPosition, IEnumerable<Payload> payloads, StringChanges stringChanges, bool forceUsingSingleAnchorPayload)
{
if (payloads != null && payloads.Any() && stringChanges != null)
{
var changes = stringChanges.GetChange(tagPosition);
changes.Payloads.AddRange(payloads);
changes.ForceUsingSingleAnchorPayload = forceUsingSingleAnchorPayload;
}
}
/// <summary>
/// Applies changes to the given string.
/// </summary>
/// <param name="seString">The string to apply changes to.</param>
/// <param name="stringChanges">The changes to apply.</param>
/// <param name="anchorPayload">The payload in the string that changes should be anchored to. If there is no anchor, the changes will be applied to the entire string.</param>
protected void ApplyStringChanges(SeString seString, StringChanges stringChanges, List<Payload> anchorPayloads = null, Payload anchorReplacePayload = null)
if (string.IsNullOrEmpty(result))
result = name;
return result;
}
/// <summary>
/// Adds the given payload changes to the dictionary.
/// </summary>
/// <param name="tagPosition">The position to add changes to.</param>
/// <param name="payloads">The payloads to add.</param>
/// <param name="stringChanges">The dictionary to add the changes to.</param>
protected void AddPayloadChanges(StringPosition tagPosition, IEnumerable<Payload> payloads, StringChanges stringChanges, bool forceUsingSingleAnchorPayload)
{
if (payloads != null && payloads.Any() && stringChanges != null)
{
var props = new StringChangesProps
{
Destination = seString,
AnchorPayload = anchorReplacePayload
};
props.AnchorPayloads = anchorPayloads;
props.StringChanges = stringChanges;
StringUpdateFactory.ApplyStringChanges(props);
}
protected void ApplyTextFormatting(GameObject gameObject, Tag tag, SeString[] destStrings, InheritableValue<bool>[] textColorApplied, List<Payload> preferedPayloads, ushort? overwriteTextColor = null)
{
if (IsTagVisible(tag, gameObject))
{
for (int i = 0; i < destStrings.Length; i++)
{
var destString = destStrings[i];
var isTextColorApplied = textColorApplied[i];
applyTextColor(destString, isTextColorApplied, tag.TextColor);
//applyTextGlowColor(destString, isTextColorApplied, tag.TextGlowColor);
//applyTextItalicColor(destString, tag.IsTextItalic); // Disabled, because that is needed only for a few parts somewhere else.
}
}
void applyTextColor(SeString destPayload, InheritableValue<bool> enableFlag, InheritableValue<ushort> colorValue)
{
var colorToUse = overwriteTextColor ?? colorValue?.InheritedValue;
if (shouldApplyFormattingPayloads(destPayload)
&& enableFlag.InheritedValue != null
&& enableFlag.InheritedValue.Value
&& colorToUse != null)
applyTextFormattingPayloads(destPayload, new UIForegroundPayload(colorToUse.Value), new UIForegroundPayload(0));
}
//void applyTextGlowColor(SeString destPayload, InheritableValue<bool> enableFlag, InheritableValue<ushort> colorValue)
//{
// if (shouldApplyFormattingPayloads(destPayload)
// && enableFlag.InheritedValue != null
// && enableFlag.InheritedValue.Value
// && colorValue.InheritedValue != null)
// applyTextFormattingPayloads(destPayload, new UIGlowPayload(colorValue.InheritedValue.Value), new UIGlowPayload(0));
//}
//void applyTextItalicColor(SeString destPayload, InheritableValue<bool> italicValue)
//{
// if (shouldApplyFormattingPayloads(destPayload)
// && italicValue.InheritedValue != null
// && italicValue.InheritedValue.Value)
// applyTextFormattingPayloads(destPayload, new EmphasisItalicPayload(true), new EmphasisItalicPayload(false));
//}
bool shouldApplyFormattingPayloads(SeString destPayload)
=> destPayload.Payloads.Any(payload => payload is TextPayload || payload is PlayerPayload);
void applyTextFormattingPayloads(SeString destPayload, Payload startPayload, Payload endPayload)
{
if (preferedPayloads == null || !preferedPayloads.Any())
applyTextFormattingPayloadToStartAndEnd(destPayload, startPayload, endPayload);
else
applyTextFormattingPayloadsToSpecificPosition(destPayload, startPayload, endPayload, preferedPayloads);
}
void applyTextFormattingPayloadToStartAndEnd(SeString destPayload, Payload startPayload, Payload endPayload)
{
destPayload.Payloads.Insert(0, startPayload);
destPayload.Payloads.Add(endPayload);
}
void applyTextFormattingPayloadsToSpecificPosition(SeString destPayload, Payload startPayload, Payload endPayload, List<Payload> preferedPayload)
{
int payloadStartIndex = destPayload.Payloads.IndexOf(preferedPayloads.First());
destPayload.Payloads.Insert(payloadStartIndex, startPayload);
int payloadEndIndex = destPayload.Payloads.IndexOf(preferedPayloads.Last());
destPayload.Payloads.Insert(payloadEndIndex + 1, endPayload);
}
var changes = stringChanges.GetChange(tagPosition);
changes.Payloads.AddRange(payloads);
changes.ForceUsingSingleAnchorPayload = forceUsingSingleAnchorPayload;
}
}
}

View File

@@ -1,145 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace PlayerTags.GameInterface
{
public static class GameInterfaceHelper
{
public static SeString ReadSeString(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return new SeString();
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
return SeString.Parse(bytes);
}
return new SeString();
}
public static bool TryReadSeString(IntPtr ptr, out SeString? seString)
{
seString = null;
if (ptr == IntPtr.Zero)
{
return false;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
seString = SeString.Parse(bytes);
return true;
}
return false;
}
public static string? ReadString(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return null;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
return Encoding.UTF8.GetString(bytes);
}
return null;
}
public static bool TryReadString(IntPtr ptr, out string? str)
{
str = null;
if (ptr == IntPtr.Zero)
{
return false;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
str = Encoding.UTF8.GetString(bytes);
return true;
}
return false;
}
public static bool TryReadStringBytes(IntPtr ptr, out byte[]? bytes)
{
bytes = null;
if (ptr == IntPtr.Zero)
{
return false;
}
var size = 0;
while (Marshal.ReadByte(ptr, size) != 0)
{
size++;
}
bytes = new byte[size];
Marshal.Copy(ptr, bytes, 0, size);
return true;
}
public static IntPtr PluginAllocate(byte[] bytes)
{
IntPtr pointer = Marshal.AllocHGlobal(bytes.Length + 1);
Marshal.Copy(bytes, 0, pointer, bytes.Length);
Marshal.WriteByte(pointer, bytes.Length, 0);
return pointer;
}
public static IntPtr PluginAllocate(SeString seString)
{
return PluginAllocate(seString.Encode());
}
public static void PluginFree(ref IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;
}
public static byte[] NullTerminate(this byte[] bytes)
{
if (bytes.Length == 0 || bytes[bytes.Length - 1] != 0)
{
var newBytes = new byte[bytes.Length + 1];
Array.Copy(bytes, newBytes, bytes.Length);
newBytes[^1] = 0;
return newBytes;
}
return bytes;
}
public static unsafe IntPtr GameUIAllocate(ulong size)
{
return (IntPtr)IMemorySpace.GetUISpace()->Malloc(size, 0);
}
public static unsafe void GameFree(ref IntPtr ptr, ulong size)
{
if (ptr == IntPtr.Zero)
{
return;
}
IMemorySpace.Free((void*)ptr, size);
ptr = IntPtr.Zero;
}
}
}

View File

@@ -1,65 +0,0 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.UI;
using Pilz.Dalamud.Nameplates;
using System;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Runtime.InteropServices;
namespace PlayerTags.GameInterface.Nameplates
{
/// <summary>
/// Provides an interface to modify nameplates.
/// </summary>
public class Nameplate : IDisposable
{
public NameplateManager NameplateManager { get; init; }
/// <summary>
/// Occurs when a player nameplate is updated by the game.
/// </summary>
public event PlayerNameplateUpdatedDelegate? PlayerNameplateUpdated;
/// <summary>
/// Whether the required hooks are in place and this instance is valid.
/// </summary>
public bool IsValid
{
get => NameplateManager != null && NameplateManager.IsValid;
}
public Nameplate()
{
NameplateManager = new();
NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged += Hooks_AddonNamePlate_SetPlayerNameManaged;
}
public void Dispose()
{
NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged -= Hooks_AddonNamePlate_SetPlayerNameManaged;
NameplateManager.Dispose();
}
private void Hooks_AddonNamePlate_SetPlayerNameManaged(Pilz.Dalamud.Nameplates.EventArgs.AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs)
{
try
{
PlayerCharacter? playerCharacter = NameplateManager.GetNameplateGameObject<PlayerCharacter>(eventArgs.SafeNameplateObject);
if (playerCharacter != null)
{
var playerNameplateUpdatedArgs = new PlayerNameplateUpdatedArgs(playerCharacter, eventArgs);
PlayerNameplateUpdated?.Invoke(playerNameplateUpdatedArgs);
}
}
catch (Exception ex)
{
PluginLog.Error(ex, $"SetPlayerNameplateDetour");
}
}
}
}

View File

@@ -1,52 +0,0 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Text.SeStringHandling;
using Pilz.Dalamud.Nameplates.EventArgs;
namespace PlayerTags.GameInterface.Nameplates
{
public class PlayerNameplateUpdatedArgs
{
private readonly AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs;
public PlayerCharacter PlayerCharacter { get; }
public SeString Name
{
get => eventArgs.Name;
}
public SeString Title
{
get => eventArgs.Title;
}
public SeString FreeCompany
{
get => eventArgs.FreeCompany;
}
public bool IsTitleVisible
{
get => eventArgs.IsTitleVisible;
set => eventArgs.IsTitleVisible = value;
}
public bool IsTitleAboveName
{
get => eventArgs.IsTitleAboveName;
set => eventArgs.IsTitleAboveName = value;
}
public int IconId
{
get => eventArgs.IconID;
set => eventArgs.IconID = value;
}
public PlayerNameplateUpdatedArgs(PlayerCharacter playerCharacter, AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs)
{
PlayerCharacter = playerCharacter;
this.eventArgs = eventArgs;
}
}
}

View File

@@ -1,4 +0,0 @@
namespace PlayerTags.GameInterface.Nameplates
{
public delegate void PlayerNameplateUpdatedDelegate(PlayerNameplateUpdatedArgs args);
}

View File

@@ -1,13 +1,12 @@
namespace PlayerTags.Inheritables
namespace PlayerTags.Inheritables;
public interface IInheritable
{
public interface IInheritable
{
public IInheritable? Parent { get; set; }
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
public InheritableBehavior Behavior { get; set; }
public abstract void SetData(InheritableData inheritableData);
public abstract void SetData(InheritableData inheritableData);
public abstract InheritableData GetData();
}
public abstract InheritableData GetData();
}

View File

@@ -1,12 +1,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace PlayerTags.Inheritables;
namespace PlayerTags.Inheritables
public enum InheritableBehavior
{
public enum InheritableBehavior
{
Inherit,
Enabled,
Disabled
}
Inherit,
Enabled,
Disabled
}

View File

@@ -2,16 +2,15 @@
using Newtonsoft.Json.Converters;
using System;
namespace PlayerTags.Inheritables
{
[Serializable]
public struct InheritableData
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("Behavior")]
public InheritableBehavior Behavior;
namespace PlayerTags.Inheritables;
[JsonProperty("Value")]
public object Value;
}
[Serializable]
public struct InheritableData
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("Behavior")]
public InheritableBehavior Behavior;
[JsonProperty("Value")]
public object Value;
}

View File

@@ -1,66 +1,63 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Collections.Generic;
namespace PlayerTags.Inheritables
namespace PlayerTags.Inheritables;
public class InheritableReference<T> : IInheritable
where T : class
{
public class InheritableReference<T> : IInheritable
where T : class
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
{
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
get
{
get
IInheritable? current = this;
while (current != null)
{
IInheritable? current = this;
while (current != null)
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableReference<T> currentOfSameType)
{
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableReference<T> currentOfSameType)
{
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
current = current.Parent;
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
return default;
current = current.Parent;
}
}
public static implicit operator InheritableReference<T>(T value) => new InheritableReference<T>(value)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableReference(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
Value = (T)inheritableData.Value;
}
public InheritableData GetData()
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
return default;
}
}
public static implicit operator InheritableReference<T>(T value) => new(value)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableReference(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
Value = (T)inheritableData.Value;
}
public InheritableData GetData()
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
}
}

View File

@@ -1,97 +1,95 @@
using Dalamud.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System;
namespace PlayerTags.Inheritables
namespace PlayerTags.Inheritables;
public class InheritableValue<T> : IInheritable
where T : struct
{
public class InheritableValue<T> : IInheritable
where T : struct
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
{
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
get
{
get
IInheritable? current = this;
while (current != null)
{
IInheritable? current = this;
while (current != null)
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableValue<T> currentOfSameType)
{
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableValue<T> currentOfSameType)
{
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
current = current.Parent;
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
return default;
current = current.Parent;
}
return default;
}
}
public static implicit operator InheritableValue<T>(T value) => new InheritableValue<T>(value)
public static implicit operator InheritableValue<T>(T value) => new(value)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
try
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
try
if (typeof(T).IsEnum && inheritableData.Value != null)
{
if (typeof(T).IsEnum && inheritableData.Value != null)
if (inheritableData.Value is string stringValue)
{
if (inheritableData.Value is string stringValue)
{
Value = (T)Enum.Parse(typeof(T), stringValue);
}
else
{
Value = (T)Enum.ToObject(typeof(T), inheritableData.Value);
}
}
else if (inheritableData.Value == null)
{
// This should never happen
PluginLog.Error($"Expected value of type {Value.GetType()} but received null");
}
else if (typeof(T) == typeof(Guid) && inheritableData.Value is string strValue)
{
Value = (T)(object)Guid.Parse(strValue);
Value = (T)Enum.Parse(typeof(T), stringValue);
}
else
{
Value = (T)Convert.ChangeType(inheritableData.Value, typeof(T));
Value = (T)Enum.ToObject(typeof(T), inheritableData.Value);
}
}
catch (Exception ex)
else if (inheritableData.Value == null)
{
PluginLog.Error(ex, $"Failed to convert {inheritableData.Value.GetType()} value '{inheritableData.Value}' to {Value.GetType()}");
// This should never happen
PluginServices.PluginLog.Error($"Expected value of type {Value.GetType()} but received null");
}
else if (typeof(T) == typeof(Guid) && inheritableData.Value is string strValue)
{
Value = (T)(object)Guid.Parse(strValue);
}
else
{
Value = (T)Convert.ChangeType(inheritableData.Value, typeof(T));
}
}
public InheritableData GetData()
catch (Exception ex)
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
PluginServices.PluginLog.Error(ex, $"Failed to convert {inheritableData.Value.GetType()} value '{inheritableData.Value}' to {Value.GetType()}");
}
}
public InheritableData GetData()
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
}
}

View File

@@ -1,62 +1,58 @@
using Dalamud.Logging;
using Dalamud.Plugin;
using PlayerTags.Resources;
using PlayerTags.Resources;
using System;
using System.ComponentModel;
using System.Globalization;
namespace PlayerTags
namespace PlayerTags;
public static class Localizer
{
public static class Localizer
public static void SetLanguage(string langCode)
{
public static void SetLanguage(string langCode)
SetLanguage(new CultureInfo(langCode));
}
public static void SetLanguage(CultureInfo cultureInfo)
{
Strings.Culture = cultureInfo;
}
public static string GetName<TEnum>(TEnum value)
{
return $"{typeof(TEnum).Name}_{value}";
}
public static string GetString<TEnum>(bool isDescription)
where TEnum : Enum
{
return GetString(typeof(TEnum).Name, isDescription);
}
public static string GetString<TEnum>(TEnum value, bool isDescription)
where TEnum : Enum
{
return GetString(GetName(value), isDescription);
}
public static string GetString(string localizedStringName, bool isDescription)
{
string localizedStringId = $"Loc_{localizedStringName}";
if (isDescription)
{
SetLanguage(new CultureInfo(langCode));
localizedStringId += "_Description";
}
public static void SetLanguage(CultureInfo cultureInfo)
{
Strings.Culture = cultureInfo;
}
return GetString(localizedStringId);
}
public static string GetName<TEnum>(TEnum value)
{
return $"{typeof(TEnum).Name}_{value}";
}
public static string GetString(string localizedStringId)
{
string? value = Strings.ResourceManager.GetString(localizedStringId, Strings.Culture);
public static string GetString<TEnum>(bool isDescription)
where TEnum : Enum
{
return GetString(typeof(TEnum).Name, isDescription);
}
if (value != null)
return value;
public static string GetString<TEnum>(TEnum value, bool isDescription)
where TEnum : Enum
{
return GetString(GetName(value), isDescription);
}
public static string GetString(string localizedStringName, bool isDescription)
{
string localizedStringId = $"Loc_{localizedStringName}";
if (isDescription)
{
localizedStringId += "_Description";
}
return GetString(localizedStringId);
}
public static string GetString(string localizedStringId)
{
string? value = Strings.ResourceManager.GetString(localizedStringId, Strings.Culture);
if (value != null)
return value;
PluginLog.Error($"Failed to get localized string for id {localizedStringId}");
return localizedStringId;
}
PluginServices.PluginLog.Error($"Failed to get localized string for id {localizedStringId}");
return localizedStringId;
}
}

View File

@@ -1,33 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace PlayerTags
namespace PlayerTags;
internal class MyPaths
{
internal class MyPaths
private static string? _PluginDirectoryPath = null;
public static string PluginDirectoryPath
{
private static string? _PluginDirectoryPath = null;
public static string PluginDirectoryPath
get
{
get
if (_PluginDirectoryPath is null)
{
if (_PluginDirectoryPath is null)
{
var path = Path.GetDirectoryName(PluginServices.DalamudPluginInterface.AssemblyLocation.FullName);
if (path is null)
_PluginDirectoryPath = string.Empty;
else
_PluginDirectoryPath = path;
}
return _PluginDirectoryPath;
var path = Path.GetDirectoryName(PluginServices.DalamudPluginInterface.AssemblyLocation.FullName);
if (path is null)
_PluginDirectoryPath = string.Empty;
else
_PluginDirectoryPath = path;
}
return _PluginDirectoryPath;
}
public static string ResourcePath
=> Path.Combine(PluginDirectoryPath, "Resources");
}
public static string ResourcePath
=> Path.Combine(PluginDirectoryPath, "Resources");
}

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>r00telement;Pilzinsel64</Authors>
<Version>1.9.0.0</Version>
<Version>1.11.1.0</Version>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<Platforms>x64</Platforms>
<Nullable>annotations</Nullable>
<LangVersion>latest</LangVersion>
@@ -24,9 +24,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dalamud.ContextMenu" Version="1.2.1" />
<PackageReference Include="DalamudPackager" Version="2.1.10" />
<ProjectReference Include="..\Pilz.Dalamud\Pilz.Dalamud.csproj" />
<PackageReference Include="DalamudPackager" Version="2.1.13" />
<ProjectReference Include="..\Pilz.Dalamud\Pilz.Dalamud\Pilz.Dalamud.csproj" />
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>
@@ -89,4 +88,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
</Project>

View File

@@ -5,5 +5,5 @@
"Punchline": "Lightweight job visibility in nameplates and chat.",
"Tags": [ "Jobs", "UI" ],
"CategoryTags": [ "jobs", "UI" ],
"RepoUrl": "https://github.com/Pilzinsel64/PlayerTags"
"RepoUrl": "https://git.pilzinsel64.de/pilzinsel64/playertags"
}

View File

@@ -1,80 +1,113 @@
using Dalamud.Game.Command;
using Dalamud.Logging;
using Dalamud.Plugin;
using Dalamud.Plugin.Internal;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using PlayerTags.Configuration;
using PlayerTags.Data;
using PlayerTags.Features;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace PlayerTags
namespace PlayerTags;
public sealed class Plugin : IDalamudPlugin
{
public sealed class Plugin : IDalamudPlugin
private const string c_CommandName = "/playertags";
private const string c_SubCommandName_EnableGlobal = "enableglobal";
private const string c_CommandArg_On = "on";
private const string c_CommandArg_Off = "off";
private const string c_CommandArg_toggle = "toggle";
private readonly PluginConfiguration pluginConfiguration = null;
private readonly PluginData pluginData = null;
private readonly PluginConfigurationUI pluginConfigurationUI = null;
private readonly CustomTagsContextMenuFeature customTagsContextMenuFeature;
private readonly NameplateTagTargetFeature nameplatesTagTargetFeature;
private readonly ChatTagTargetFeature chatTagTargetFeature;
public Plugin(IDalamudPluginInterface pluginInterface)
{
public string Name => "Player Tags";
private const string c_CommandName = "/playertags";
PluginServices.Initialize(pluginInterface);
Pilz.Dalamud.PluginServices.Initialize(pluginInterface);
private PluginConfiguration m_PluginConfiguration;
private PluginData m_PluginData;
private PluginConfigurationUI m_PluginConfigurationUI;
pluginConfiguration = PluginConfiguration.LoadPluginConfig() ?? new();
pluginData = new PluginData(pluginConfiguration);
pluginConfigurationUI = new PluginConfigurationUI(pluginConfiguration, pluginData);
private CustomTagsContextMenuFeature m_CustomTagsContextMenuFeature;
private NameplateTagTargetFeature m_NameplatesTagTargetFeature;
private ChatTagTargetFeature m_ChatTagTargetFeature;
Localizer.SetLanguage(PluginServices.DalamudPluginInterface.UiLanguage);
PluginServices.DalamudPluginInterface.LanguageChanged += DalamudPluginInterface_LanguageChanged;
public Plugin(DalamudPluginInterface pluginInterface)
PluginServices.DalamudPluginInterface.UiBuilder.Draw += UiBuilder_Draw;
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
PluginServices.CommandManager.AddHandler(c_CommandName, new CommandInfo(CommandManager_Handler)
{
PluginServices.Initialize(pluginInterface);
Pilz.Dalamud.PluginServices.Initialize(pluginInterface);
HelpMessage = Resources.Strings.Loc_Command_playertags_v2
});
customTagsContextMenuFeature = new CustomTagsContextMenuFeature(pluginConfiguration, pluginData);
nameplatesTagTargetFeature = new NameplateTagTargetFeature(pluginConfiguration, pluginData);
chatTagTargetFeature = new ChatTagTargetFeature(pluginConfiguration, pluginData);
}
m_PluginConfiguration = PluginConfiguration.LoadPluginConfig() ?? new PluginConfiguration();
m_PluginData = new PluginData(m_PluginConfiguration);
m_PluginConfigurationUI = new PluginConfigurationUI(m_PluginConfiguration, m_PluginData);
public void Dispose()
{
chatTagTargetFeature.Dispose();
nameplatesTagTargetFeature.Dispose();
customTagsContextMenuFeature.Dispose();
PluginServices.DalamudPluginInterface.LanguageChanged -= DalamudPluginInterface_LanguageChanged;
PluginServices.CommandManager.RemoveHandler(c_CommandName);
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
PluginServices.DalamudPluginInterface.UiBuilder.Draw -= UiBuilder_Draw;
}
Localizer.SetLanguage(PluginServices.DalamudPluginInterface.UiLanguage);
PluginServices.DalamudPluginInterface.LanguageChanged += DalamudPluginInterface_LanguageChanged;
private void DalamudPluginInterface_LanguageChanged(string langCode)
{
Localizer.SetLanguage(langCode);
}
PluginServices.DalamudPluginInterface.UiBuilder.Draw += UiBuilder_Draw;
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
PluginServices.CommandManager.AddHandler(c_CommandName, new CommandInfo((string command, string arguments) => UiBuilder_OpenConfigUi())
{
HelpMessage = Resources.Strings.Loc_Command_playertags
});
m_CustomTagsContextMenuFeature = new CustomTagsContextMenuFeature(m_PluginConfiguration, m_PluginData);
m_NameplatesTagTargetFeature = new NameplateTagTargetFeature(m_PluginConfiguration, m_PluginData);
m_ChatTagTargetFeature = new ChatTagTargetFeature(m_PluginConfiguration, m_PluginData);
}
public void Dispose()
private void CommandManager_Handler(string command, string arguments)
{
switch (command)
{
m_ChatTagTargetFeature.Dispose();
m_NameplatesTagTargetFeature.Dispose();
m_CustomTagsContextMenuFeature.Dispose();
PluginServices.DalamudPluginInterface.LanguageChanged -= DalamudPluginInterface_LanguageChanged;
PluginServices.CommandManager.RemoveHandler(c_CommandName);
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
PluginServices.DalamudPluginInterface.UiBuilder.Draw -= UiBuilder_Draw;
}
private void DalamudPluginInterface_LanguageChanged(string langCode)
{
Localizer.SetLanguage(langCode);
}
private void UiBuilder_Draw()
{
if (m_PluginConfiguration.IsVisible)
m_PluginConfigurationUI.Draw();
}
private void UiBuilder_OpenConfigUi()
{
m_PluginConfiguration.IsVisible = true;
m_PluginConfiguration.Save(m_PluginData);
case c_CommandName:
if (string.IsNullOrWhiteSpace(arguments))
UiBuilder_OpenConfigUi();
else
{
var lowerArgs = arguments.ToLower().Split(' ');
if (lowerArgs.Length >= 1)
{
switch (lowerArgs[0])
{
case c_SubCommandName_EnableGlobal:
if (lowerArgs.Length >= 2)
{
switch (lowerArgs[0])
{
case c_CommandArg_On:
pluginConfiguration.EnabledGlobal = true;
break;
case c_CommandArg_Off:
pluginConfiguration.EnabledGlobal = false;
break;
case c_CommandArg_toggle:
pluginConfiguration.EnabledGlobal = !pluginConfiguration.EnabledGlobal;
break;
}
}
break;
}
}
}
break;
}
}
private void UiBuilder_Draw()
{
if (pluginConfiguration.IsVisible)
pluginConfigurationUI.Draw();
}
private void UiBuilder_OpenConfigUi()
{
pluginConfiguration.IsVisible = true;
pluginConfiguration.Save(pluginData);
}
}

View File

@@ -1,30 +1,29 @@
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Party;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.IoC;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Pilz.Dalamud.NamePlate;
namespace PlayerTags
namespace PlayerTags;
public class PluginServices
{
public class PluginServices
{
[PluginService] public static ChatGui ChatGui { get; set; } = null!;
[PluginService] public static ClientState ClientState { get; set; } = null!;
[PluginService] public static CommandManager CommandManager { get; set; } = null!;
[PluginService] public static DalamudPluginInterface DalamudPluginInterface { get; set; } = null!;
[PluginService] public static DataManager DataManager { get; set; } = null!;
[PluginService] public static Framework Framework { get; set; } = null!;
[PluginService] public static GameGui GameGui { get; set; } = null!;
[PluginService] public static ObjectTable ObjectTable { get; set; } = null!;
[PluginService] public static PartyList PartyList { get; set; } = null!;
[PluginService] public static IDalamudPluginInterface DalamudPluginInterface { get; set; }
[PluginService] public static IPluginLog PluginLog { get; set; }
[PluginService] public static IGameConfig GameConfig { get; set; }
[PluginService] public static IChatGui ChatGui { get; set; }
[PluginService] public static IClientState ClientState { get; set; }
[PluginService] public static ICommandManager CommandManager { get; set; }
[PluginService] public static IDataManager DataManager { get; set; }
[PluginService] public static IFramework Framework { get; set; }
[PluginService] public static IGameGui GameGui { get; set; }
[PluginService] public static IObjectTable ObjectTable { get; set; }
[PluginService] public static IPartyList PartyList { get; set; }
[PluginService] public static IGameInteropProvider GameInteropProvider { get; set; }
[PluginService] public static IContextMenu ContextMenu { get; set; }
[PluginService] public static INamePlateGui NamePlateGui => INamePlateGui.Instance;
public static void Initialize(DalamudPluginInterface pluginInterface)
{
pluginInterface.Create<PluginServices>();
}
public static void Initialize(IDalamudPluginInterface pluginInterface)
{
pluginInterface.Create<PluginServices>();
}
}

View File

@@ -1,7 +1,6 @@
namespace PlayerTags.PluginStrings
namespace PlayerTags.PluginStrings;
public interface IPluginString
{
public interface IPluginString
{
public string Value { get; }
}
public string Value { get; }
}

View File

@@ -1,18 +1,17 @@
namespace PlayerTags.PluginStrings
namespace PlayerTags.PluginStrings;
public class LiteralPluginString : IPluginString
{
public class LiteralPluginString : IPluginString
private string m_Value;
public string Value => m_Value;
public LiteralPluginString(string value)
{
private string m_Value;
public string Value => m_Value;
m_Value = value;
}
public LiteralPluginString(string value)
{
m_Value = value;
}
public override string ToString()
{
return Value;
}
public override string ToString()
{
return Value;
}
}

View File

@@ -1,18 +1,17 @@
namespace PlayerTags.PluginStrings
namespace PlayerTags.PluginStrings;
public class LocalizedPluginString : IPluginString
{
public class LocalizedPluginString : IPluginString
public string Key { get; init; }
public string Value => Localizer.GetString(Key, false);
public LocalizedPluginString(string key)
{
public string Key { get; init; }
public string Value => Localizer.GetString(Key, false);
Key = key;
}
public LocalizedPluginString(string key)
{
Key = key;
}
public override string ToString()
{
return Value;
}
public override string ToString()
{
return Value;
}
}

View File

@@ -1,116 +1,113 @@
using Dalamud.Logging;
using System;
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
namespace PlayerTags
namespace PlayerTags;
/// <summary>
/// Generates names based on existing lists of words.
/// </summary>
public static class RandomNameGenerator
{
/// <summary>
/// Generates names based on existing lists of words.
/// </summary>
public static class RandomNameGenerator
private static string[]? s_Adjectives;
private static string[] Adjectives
{
private static string[]? s_Adjectives;
private static string[] Adjectives
get
{
get
if (s_Adjectives == null)
{
if (s_Adjectives == null)
try
{
try
{
s_Adjectives = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.AdjectivesTxt));
}
catch (Exception ex)
{
PluginLog.Error(ex, $"RandomNameGenerator failed to read adjectives");
}
s_Adjectives = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.AdjectivesTxt));
}
if (s_Adjectives != null)
catch (Exception ex)
{
return s_Adjectives;
PluginServices.PluginLog.Error(ex, $"RandomNameGenerator failed to read adjectives");
}
return new string[] { };
}
if (s_Adjectives != null)
{
return s_Adjectives;
}
return new string[] { };
}
}
private static string[]? s_Nouns;
private static string[] Nouns
{
get
{
if (s_Nouns == null)
{
try
{
s_Nouns = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.NounsTxt));
}
catch (Exception ex)
{
PluginServices.PluginLog.Error(ex, $"RandomNameGenerator failed to read nouns");
}
}
if (s_Nouns != null)
{
return s_Nouns;
}
return new string[] { };
}
}
/// <summary>
/// Generates a name for the given string.
/// </summary>
/// <param name="str">The string to generate a name for.</param>
/// <returns>A generated name.</returns>
public static string? Generate(string str)
{
if (Adjectives == null || Nouns == null)
{
return null;
}
private static string[]? s_Nouns;
private static string[] Nouns
int hash = GetDeterministicHashCode(str);
// Use the seed as the hash so the same player always gets the same name
Random random = new(hash);
var adjective = Adjectives[random.Next(0, Adjectives.Length)];
var noun = Nouns[random.Next(0, Nouns.Length)];
var generatedName = $"{adjective} {noun}";
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(generatedName);
}
/// <summary>
/// Gets a deterministic hash code for the given string.
/// </summary>
/// <param name="str">The string to hash.</param>
/// <returns>A deterministic hash code.</returns>
private static int GetDeterministicHashCode(string str)
{
unchecked
{
get
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;
for (int index = 0; index < str.Length; index += 2)
{
if (s_Nouns == null)
hash1 = ((hash1 << 5) + hash1) ^ str[index];
if (index == str.Length - 1)
{
try
{
s_Nouns = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.NounsTxt));
}
catch (Exception ex)
{
PluginLog.Error(ex, $"RandomNameGenerator failed to read nouns");
}
break;
}
if (s_Nouns != null)
{
return s_Nouns;
}
return new string[] { };
}
}
/// <summary>
/// Generates a name for the given string.
/// </summary>
/// <param name="str">The string to generate a name for.</param>
/// <returns>A generated name.</returns>
public static string? Generate(string str)
{
if (Adjectives == null || Nouns == null)
{
return null;
hash2 = ((hash2 << 5) + hash2) ^ str[index + 1];
}
int hash = GetDeterministicHashCode(str);
// Use the seed as the hash so the same player always gets the same name
Random random = new Random(hash);
var adjective = Adjectives[random.Next(0, Adjectives.Length)];
var noun = Nouns[random.Next(0, Nouns.Length)];
var generatedName = $"{adjective} {noun}";
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(generatedName);
}
/// <summary>
/// Gets a deterministic hash code for the given string.
/// </summary>
/// <param name="str">The string to hash.</param>
/// <returns>A deterministic hash code.</returns>
private static int GetDeterministicHashCode(string str)
{
unchecked
{
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;
for (int index = 0; index < str.Length; index += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ str[index];
if (index == str.Length - 1)
{
break;
}
hash2 = ((hash2 << 5) + hash2) ^ str[index + 1];
}
return hash1 + (hash2 * 1566083941);
}
return hash1 + (hash2 * 1566083941);
}
}
}

View File

@@ -187,11 +187,13 @@ namespace PlayerTags.Resources {
}
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Shows the config window for Player Tags ähnelt.
/// Sucht eine lokalisierte Zeichenfolge, die Shows the config window for Player Tags
///Subcommands:
///enableglobal on|of|toggle -&gt; Set a global master switch that enables or disables all plugin features without changing the current configuration. ähnelt.
/// </summary>
public static string Loc_Command_playertags {
public static string Loc_Command_playertags_v2 {
get {
return ResourceManager.GetString("Loc_Command_playertags", resourceCulture);
return ResourceManager.GetString("Loc_Command_playertags_v2", resourceCulture);
}
}

View File

@@ -829,4 +829,9 @@ Benutze dies, wenn du jede Option unter deiner Kontrolle haben möchtest oder nu
<value>Wenn aktiviert, gilt das Tag für alle Chat-Nachrichten, die keinen definierten Typ haben.
Dieser Fall kann entweder auftreten, wenn das Spiel ein Update bekommt und die Aufzählung aller Chat-Typen aufgrund von geänderten Werten ungültig sind oder Plugins erstellt einen benutzerdefinierten Chat-Typ, aus welchen Gründen auch immer.</value>
</data>
<data name="Loc_Command_playertags_v2" xml:space="preserve">
<value>Zeigt das Konfigurationsfester Player Tags
Unterbefehle:
enableglobal on|of|toggle -&gt; Setzt einen globalen Hauptschalter, welcher alle PluginFeaturues ein- bzw. ausschalten kann, ohne die Konfiguration selbst zu ändern.</value>
</data>
</root>

View File

@@ -829,4 +829,9 @@ Utilisez ce modèle si vous souhaitez avoir toutes les options sous votre contr
<value>Lorsque cette option est activée, le tag s'appliquera à tous les messages qui n'ont pas de type défini.
Cela peut se produire si les mises à jour du jeu et l'énumération de tous les types de chat deviennent invalides en raison de valeurs déplacées ou altérées, ou si un autre plugin crée un type de chat personnalisé pour quelque raison que ce soit.</value>
</data>
<data name="Loc_Command_playertags_v2" xml:space="preserve">
<value>Affiche la fenêtre de configuration des tags du joueur
Sous-commandes :
enableglobal on|of|toggle -&gt; Commande globale maîtresse qui active ou désactive toutes les fonctionnalités du plugin sans en modifier la configuration actuelle.</value>
</data>
</root>

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Loc_Static_PluginName" xml:space="preserve">
<value>플레이어 태그</value>
</data>
<data name="Loc_Static_WarningMessage" xml:space="preserve">
<value>이 플러그인은 네임태그 및 채팅을 수정할 수 있습니다. 해당 기능을 수정하는 다른 플러그인과 동시에 사용하면 예기치 못한 결과가 나올 수 있습니다. 플러그인 로드 순서에 따라 달라집니다.</value>
</data>
<data name="Loc_Static_General" xml:space="preserve">
<value>기본</value>
</data>
<data name="Loc_Static_QuickTag" xml:space="preserve">
<value>퀵 태그</value>
</data>
<data name="Loc_Static_TaggedPlayers" xml:space="preserve">
<value>태그된 플레이어</value>
</data>
<data name="Loc_Static_PlayerName" xml:space="preserve">
<value>플레이어</value>
</data>
<data name="Loc_Static_ContextMenu_AddTag" xml:space="preserve">
<value>태그 추가: {0}</value>
</data>
<data name="Loc_Static_ContextMenu_RemoveTag" xml:space="preserve">
<value>태그 지우기: {0}</value>
</data>
<data name="Loc_Static_Nameplates" xml:space="preserve">
<value>네임태그</value>
</data>
<data name="Loc_Static_Format_AddTagToPlayer" xml:space="preserve">
<value>추가 '{0}' 또는 {1}.</value>
</data>
<data name="Loc_IsSelected" xml:space="preserve">
<value>선택됨</value>
</data>
<data name="Loc_IsExpanded" xml:space="preserve">
<value>펼침</value>
</data>
<data name="Loc_IsCustomTagsContextMenuEnabled" xml:space="preserve">
<value>상황에 맞는 메뉴 통합</value>
</data>
<data name="Loc_IsCustomTagsContextMenuEnabled_Description" xml:space="preserve">
<value>플레이어에서 사용자 지정 태그를 추가 혹은 제거하기 위한 옵션을 상황에 맞는 메뉴에서 사용할 수 있습니다.</value>
</data>
<data name="Loc_IsShowInheritedPropertiesEnabled" xml:space="preserve">
<value>상속 속성 표시</value>
</data>
<data name="Loc_IsShowInheritedPropertiesEnabled_Description" xml:space="preserve">
<value>상위 노드에서 상속된 속성이 편집기에 표시됩니다.</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility" xml:space="preserve">
<value>자유부대 표시</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Default" xml:space="preserve">
<value>기본</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Default_Description" xml:space="preserve">
<value>네임태그의 자유부대는 캐릭터가 자유부대에 속해 있을 때만 볼 수 있습니다.</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Never" xml:space="preserve">
<value>안 보이게 하기</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Never_Description" xml:space="preserve">
<value>자유부대를 보이지 않게 합니다.</value>
</data>
<data name="Loc_NameplateTitleVisibility" xml:space="preserve">
<value>칭호 표시</value>
</data>
<data name="Loc_NameplateTitleVisibility_Always" xml:space="preserve">
<value>항상 표시</value>
</data>
<data name="Loc_NameplateTitleVisibility_Always_Description" xml:space="preserve">
<value>네임태그의 칭호는 캐릭터에게 칭호가 없는 경우에도 볼 수 있습니다.</value>
</data>
<data name="Loc_NameplateTitleVisibility_Default" xml:space="preserve">
<value>기본</value>
</data>
<data name="Loc_NameplateTitleVisibility_Never" xml:space="preserve">
<value>안 보이게 하기</value>
</data>
</root>

View File

@@ -829,7 +829,9 @@ Use this if you want to to have every option under your control or just want to
<value>When enabled the Tag will apply to all Chat messages that has not a defined type.
This case can happen either if the Game updates and the Enumeration of all Chat Types gets invalid due to shifted values, or plugins creates a custom chat type for whatever reason.</value>
</data>
<data name="Loc_Command_playertags" xml:space="preserve">
<value>Shows the config window for Player Tags</value>
<data name="Loc_Command_playertags_v2" xml:space="preserve">
<value>Shows the config window for Player Tags
Subcommands:
enableglobal on|of|toggle -&gt; Set a global master switch that enables or disables all plugin features without changing the current configuration.</value>
</data>
</root>

View File

@@ -0,0 +1,837 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Loc_Static_PluginName" xml:space="preserve">
<value>玩家标签</value>
</data>
<data name="Loc_Static_WarningMessage" xml:space="preserve">
<value>此插件可以修改姓名版和聊天栏。当与其他修改这些内容的插件同时使用时,您可能会看到预期之外的行为。 您的插件的加载顺序可能会影响这个现象。</value>
</data>
<data name="Loc_Static_General" xml:space="preserve">
<value>通常设置</value>
</data>
<data name="Loc_Static_QuickTag" xml:space="preserve">
<value>快速标签[国服好像不可用]</value>
</data>
<data name="Loc_Static_TaggedPlayers" xml:space="preserve">
<value>已标记的玩家</value>
</data>
<data name="Loc_Static_PlayerName" xml:space="preserve">
<value>玩家</value>
</data>
<data name="Loc_Static_ContextMenu_AddTag" xml:space="preserve">
<value>添加标签: {0}</value>
</data>
<data name="Loc_Static_ContextMenu_RemoveTag" xml:space="preserve">
<value>移除标签: {0}</value>
</data>
<data name="Loc_Static_Nameplates" xml:space="preserve">
<value>铭牌</value>
</data>
<data name="Loc_Static_Format_AddTagToPlayer" xml:space="preserve">
<value>将 {0}’添加到 {1}.</value>
</data>
<data name="Loc_Static_Inherited" xml:space="preserve">
<value>&lt;已继承&gt;</value>
</data>
<data name="Loc_IsSelected" xml:space="preserve">
<value>已选择</value>
</data>
<data name="Loc_IsExpanded" xml:space="preserve">
<value>已展开</value>
</data>
<data name="Loc_IsCustomTagsContextMenuEnabled" xml:space="preserve">
<value>上下文菜单集成</value>
</data>
<data name="Loc_IsCustomTagsContextMenuEnabled_Description" xml:space="preserve">
<value>选项将在上下文菜单中可用,用于添加和删除玩家的自定义标签。</value>
</data>
<data name="Loc_IsShowInheritedPropertiesEnabled" xml:space="preserve">
<value>显示继承的属性</value>
</data>
<data name="Loc_IsShowInheritedPropertiesEnabled_Description" xml:space="preserve">
<value>从父节点继承的属性将显示在编辑器中。</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility" xml:space="preserve">
<value>部队名称可见性</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Default" xml:space="preserve">
<value>默认</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Default_Description" xml:space="preserve">
<value>铭牌上的部队名称部分只有当角色已加入部队时才可见。</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Never" xml:space="preserve">
<value>不可见</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Never_Description" xml:space="preserve">
<value>铭牌上的部队名称部分将永远不可见。</value>
</data>
<data name="Loc_NameplateTitleVisibility" xml:space="preserve">
<value>称号可见性</value>
</data>
<data name="Loc_NameplateTitleVisibility_Always" xml:space="preserve">
<value>始终可见</value>
</data>
<data name="Loc_NameplateTitleVisibility_Always_Description" xml:space="preserve">
<value>铭牌上的标题将始终可见,即使角色没有标题。</value>
</data>
<data name="Loc_NameplateTitleVisibility_Default" xml:space="preserve">
<value>默认</value>
</data>
<data name="Loc_NameplateTitleVisibility_Default_Description" xml:space="preserve">
<value>只有当角色有头衔时,铭牌上的头衔才会可见。</value>
</data>
<data name="Loc_NameplateTitleVisibility_Never" xml:space="preserve">
<value>不可见</value>
</data>
<data name="Loc_NameplateTitleVisibility_Never_Description" xml:space="preserve">
<value>铭牌上的称号部分将永远不可见。</value>
</data>
<data name="Loc_NameplateTitleVisibility_WhenHasTags" xml:space="preserve">
<value>仅当有标签时可见</value>
</data>
<data name="Loc_NameplateTitleVisibility_WhenHasTags_Description" xml:space="preserve">
<value>铭牌上的称号部分只在有标签时才可见。</value>
</data>
<data name="Loc_NameplateTitlePosition" xml:space="preserve">
<value>称号位置</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysAboveName" xml:space="preserve">
<value>总是在名称上字</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysAboveName_Description" xml:space="preserve">
<value>铭牌上的称号将始终位于名字上方。</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysBelowName" xml:space="preserve">
<value>总是在名字下面</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysBelowName_Description" xml:space="preserve">
<value>铭牌上的称号将始终位于名字下方。</value>
</data>
<data name="Loc_NameplateTitlePosition_Default" xml:space="preserve">
<value>默认</value>
</data>
<data name="Loc_NameplateTitlePosition_Default_Description" xml:space="preserve">
<value>铭牌上的称号将根据称号定位。</value>
</data>
<data name="Loc_NameplateElement_FreeCompany" xml:space="preserve">
<value>部队</value>
</data>
<data name="Loc_NameplateElement_FreeCompany_Description" xml:space="preserve">
<value>铭牌上的部队名称部分。</value>
</data>
<data name="Loc_NameplateElement_Name" xml:space="preserve">
<value>名字</value>
</data>
<data name="Loc_NameplateElement_Name_Description" xml:space="preserve">
<value>铭牌上的名字部分。</value>
</data>
<data name="Loc_NameplateElement_Title" xml:space="preserve">
<value>称号</value>
</data>
<data name="Loc_NameplateElement_Title_Description" xml:space="preserve">
<value>铭牌上的称号部分。</value>
</data>
<data name="Loc_Static_Development" xml:space="preserve">
<value>开发</value>
</data>
<data name="Loc_Static_Experimental" xml:space="preserve">
<value>实验</value>
</data>
<data name="Loc_IsPlayerNameRandomlyGenerated" xml:space="preserve">
<value>随机生成玩家名字</value>
</data>
<data name="Loc_IsPlayerNameRandomlyGenerated_Description" xml:space="preserve">
<value>将每个玩家的名字替换为随机生成的名字。</value>
</data>
<data name="Loc_IsLinkSelfInChatEnabled" xml:space="preserve">
<value>在聊天中为自己应用标签</value>
</data>
<data name="Loc_IsLinkSelfInChatEnabled_Description" xml:space="preserve">
<value>尝试将您的聊天名称链接到您的角色,允许在聊天中将标签应用于自己。</value>
</data>
<data name="Loc_IsApplyTagsToAllChatMessagesEnabled" xml:space="preserve">
<value>将标签应用到所有聊天信息</value>
</data>
<data name="Loc_IsApplyTagsToAllChatMessagesEnabled_Description" xml:space="preserve">
<value>将标签应用于所有聊天消息,包括非社交消息。</value>
</data>
<data name="Loc_Static_ResetDefault_Description" xml:space="preserve">
<value>重置此项为默认值。</value>
</data>
<data name="Loc_Static_ResetAllDefault_Description" xml:space="preserve">
<value>重置所有项为默认值。自定义标签不会被触及。</value>
</data>
<data name="Loc_Static_Tags" xml:space="preserve">
<value>标签</value>
</data>
<data name="Loc_Static_NoText" xml:space="preserve">
<value>&lt;无文本&gt;</value>
</data>
<data name="Loc_AllTags" xml:space="preserve">
<value>全部</value>
</data>
<data name="Loc_AllRoleTags" xml:space="preserve">
<value>角色</value>
</data>
<data name="Loc_Role_LandHand" xml:space="preserve">
<value>地/手</value>
</data>
<data name="Loc_Role_Tank" xml:space="preserve">
<value>坦克</value>
</data>
<data name="Loc_Role_Healer" xml:space="preserve">
<value>治疗</value>
</data>
<data name="Loc_Role_Dps" xml:space="preserve">
<value>每秒伤害</value>
</data>
<data name="Loc_DpsRole_Melee" xml:space="preserve">
<value>混战</value>
</data>
<data name="Loc_DpsRole_Ranged" xml:space="preserve">
<value>远程</value>
</data>
<data name="Loc_RangedDpsRole_Magical" xml:space="preserve">
<value>魔法</value>
</data>
<data name="Loc_RangedDpsRole_Physical" xml:space="preserve">
<value>物理</value>
</data>
<data name="Loc_LandHandRole_Hand" xml:space="preserve">
<value>手部</value>
</data>
<data name="Loc_LandHandRole_Land" xml:space="preserve">
<value>大地</value>
</data>
<data name="Loc_AllCustomTags" xml:space="preserve">
<value>自定义</value>
</data>
<data name="Loc_CustomTags" xml:space="preserve">
<value>自定义</value>
</data>
<data name="Loc_IsEnabled_Description" xml:space="preserve">
<value>启用此覆盖的值。</value>
</data>
<data name="Loc_Static_AddPropertyOverride_Description" xml:space="preserve">
<value>添加属性覆盖。</value>
</data>
<data name="Loc_Static_RemovePropertyOverride_Description" xml:space="preserve">
<value>移除此属性覆盖。该值将从父项继承。</value>
</data>
<data name="Loc_Static_NewTag" xml:space="preserve">
<value>新标签</value>
</data>
<data name="Loc_Static_AddCustomTag_Description" xml:space="preserve">
<value>添加自定义标签。</value>
</data>
<data name="Loc_Static_RemoveCustomTag_Description" xml:space="preserve">
<value>移除此自定义标签。</value>
</data>
<data name="Loc_Icon" xml:space="preserve">
<value>图标</value>
</data>
<data name="Loc_Icon_Description" xml:space="preserve">
<value>将显示的图标。</value>
</data>
<data name="Loc_IsIconVisibleInChat" xml:space="preserve">
<value>在聊天中显示</value>
</data>
<data name="Loc_IsRoleIconVisibleInChat_Description" xml:space="preserve">
<value>是否在聊天中显示角色图标。</value>
</data>
<data name="Loc_IsRoleIconVisibleInNameplates" xml:space="preserve">
<value>在铭牌上显示角色图标</value>
</data>
<data name="Loc_IsRoleIconVisibleInNameplates_Description" xml:space="preserve">
<value>是否在铭牌上显示角色图标。</value>
</data>
<data name="Loc_Text" xml:space="preserve">
<value>文本</value>
</data>
<data name="Loc_Text_Description" xml:space="preserve">
<value>将显示的文本。</value>
</data>
<data name="Loc_TextColor" xml:space="preserve">
<value>颜色</value>
</data>
<data name="Loc_TextColor_Description" xml:space="preserve">
<value>文本的颜色。</value>
</data>
<data name="Loc_TextGlowColor" xml:space="preserve">
<value>发光颜色</value>
</data>
<data name="Loc_TextGlowColor_Description" xml:space="preserve">
<value>文本的发光颜色。</value>
</data>
<data name="Loc_IsTextItalic" xml:space="preserve">
<value>斜体</value>
</data>
<data name="Loc_IsTextItalic_Description" xml:space="preserve">
<value>文本是否为斜体。</value>
</data>
<data name="Loc_IsTextVisibleInChat" xml:space="preserve">
<value>在聊天中显示</value>
</data>
<data name="Loc_IsTextVisibleInChat_Description" xml:space="preserve">
<value>文本是否会在聊天中显示。</value>
</data>
<data name="Loc_IsTextVisibleInNameplates" xml:space="preserve">
<value>在铭牌上显示</value>
</data>
<data name="Loc_IsTextVisibleInNameplates_Description" xml:space="preserve">
<value>文本是否会显示在铭牌上。</value>
</data>
<data name="Loc_TagPositionInChat" xml:space="preserve">
<value>聊天窗口中的位置</value>
</data>
<data name="Loc_TagPositionInChat_Description" xml:space="preserve">
<value>标签将放置在聊天中的位置。</value>
</data>
<data name="Loc_TagPositionInNameplates" xml:space="preserve">
<value>铭牌中的位置</value>
</data>
<data name="Loc_TagPositionInNameplates_Description" xml:space="preserve">
<value>标签将放置在铭牌上的位置。</value>
</data>
<data name="Loc_TagTargetInNameplates" xml:space="preserve">
<value>铭牌上的目标</value>
</data>
<data name="Loc_TagTargetInNameplates_Description" xml:space="preserve">
<value>标签应在铭牌中针对的元素。</value>
</data>
<data name="Loc_GameObjectNamesToApplyTo" xml:space="preserve">
<value>添加到玩家</value>
</data>
<data name="Loc_GameObjectNamesToApplyTo_Description" xml:space="preserve">
<value>要添加玩家列表的标签,以逗号或分号分隔。例如 “Cloud Strife, Tifa Lockhart”。</value>
</data>
<data name="Loc_TagPosition_After" xml:space="preserve">
<value>之后</value>
</data>
<data name="Loc_TagPosition_After_Description" xml:space="preserve">
<value>在目标元素之后显示标签。</value>
</data>
<data name="Loc_TagPosition_Before" xml:space="preserve">
<value>之前</value>
</data>
<data name="Loc_TagPosition_Before_Description" xml:space="preserve">
<value>在目标元素之前显示标签。</value>
</data>
<data name="Loc_TagPosition_Replace" xml:space="preserve">
<value>替换</value>
</data>
<data name="Loc_TagPosition_Replace_Description" xml:space="preserve">
<value>将目标元素替换为标签。</value>
</data>
<data name="Loc_IsEnabled" xml:space="preserve">
<value>已启用</value>
</data>
<data name="Loc_CustomId" xml:space="preserve">
<value>自定义ID</value>
</data>
<data name="Loc_GeneralCategory" xml:space="preserve">
<value>通用属性</value>
</data>
<data name="Loc_IconCategory" xml:space="preserve">
<value>图标属性</value>
</data>
<data name="Loc_TextCategory" xml:space="preserve">
<value>文本属性</value>
</data>
<data name="Loc_PositionCategory" xml:space="preserve">
<value>位置属性</value>
</data>
<data name="Loc_ActivityCategory" xml:space="preserve">
<value>活动属性</value>
</data>
<data name="Loc_PlayerCategory" xml:space="preserve">
<value>玩家属性</value>
</data>
<data name="Loc_IsVisibleInPveDuties" xml:space="preserve">
<value>在 PvE 任务中显示</value>
</data>
<data name="Loc_IsVisibleInPveDuties_Description" xml:space="preserve">
<value>标签是否应该在 PvE 任务中可见。</value>
</data>
<data name="Loc_IsVisibleInPvpDuties" xml:space="preserve">
<value>在 PvP 任务中显示</value>
</data>
<data name="Loc_IsVisibleInPvpDuties_Description" xml:space="preserve">
<value>标签是否应该在 PvP 任务中可见。</value>
</data>
<data name="Loc_IsVisibleInOverworld" xml:space="preserve">
<value>在别处显示</value>
</data>
<data name="Loc_IsVisibleInOverworld_Description" xml:space="preserve">
<value>标签是否应该在没有特定选项的其他情况下可见。</value>
</data>
<data name="Loc_IsVisibleForSelf" xml:space="preserve">
<value>为自己显示</value>
</data>
<data name="Loc_IsVisibleForSelf_Description" xml:space="preserve">
<value>标签是否对本地玩家可见。</value>
</data>
<data name="Loc_IsVisibleForPartyPlayers" xml:space="preserve">
<value>显示给组队成员</value>
</data>
<data name="Loc_IsVisibleForPartyPlayers_Description" xml:space="preserve">
<value>标签是否应该对组队成员可见。</value>
</data>
<data name="Loc_IsVisibleForAlliancePlayers" xml:space="preserve">
<value>显示给联盟成员</value>
</data>
<data name="Loc_IsVisibleForAlliancePlayers_Description" xml:space="preserve">
<value>标签是否对不在当前组队中的联盟成员可见。</value>
</data>
<data name="Loc_IsVisibleForFriendPlayers" xml:space="preserve">
<value>显示给朋友</value>
</data>
<data name="Loc_IsVisibleForFriendPlayers_Description" xml:space="preserve">
<value>标签是否应该对朋友可见。</value>
</data>
<data name="Loc_IsVisibleForEnemyPlayers" xml:space="preserve">
<value>显示给敌人</value>
</data>
<data name="Loc_IsVisibleForEnemyPlayers_Description" xml:space="preserve">
<value>标签是否应该对 PvP 中的敌人可见。</value>
</data>
<data name="Loc_IsVisibleForOtherPlayers" xml:space="preserve">
<value>显示给其他人</value>
</data>
<data name="Loc_IsVisibleForOtherPlayers_Description" xml:space="preserve">
<value>在没有特定选项的其他情况下,标签是否应该对玩家可见。</value>
</data>
<data name="Loc_IsPlayersTabOrderedByProximity" xml:space="preserve">
<value>按距离排序</value>
</data>
<data name="Loc_IsPlayersTabOrderedByProximity_Description" xml:space="preserve">
<value>离您较近的玩家将被排序到顶部。</value>
</data>
<data name="Loc_IsPlayersTabSelfVisible" xml:space="preserve">
<value>显示自己</value>
</data>
<data name="Loc_IsPlayersTabSelfVisible_Description" xml:space="preserve">
<value>在玩家列表中显示您自己。</value>
</data>
<data name="Loc_IsPlayersTabFriendsVisible" xml:space="preserve">
<value>显示朋友</value>
</data>
<data name="Loc_IsPlayersTabFriendsVisible_Description" xml:space="preserve">
<value>在玩家列表中显示朋友。</value>
</data>
<data name="Loc_IsPlayersTabPartyVisible" xml:space="preserve">
<value>显示组队成员</value>
</data>
<data name="Loc_IsPlayersTabPartyVisible_Description" xml:space="preserve">
<value>在玩家列表中显示组队成员。</value>
</data>
<data name="Loc_IsPlayersTabAllianceVisible" xml:space="preserve">
<value>显示联盟成员</value>
</data>
<data name="Loc_IsPlayersTabAllianceVisible_Description" xml:space="preserve">
<value>在玩家列表中显示联盟成员。</value>
</data>
<data name="Loc_IsPlayersTabEnemiesVisible" xml:space="preserve">
<value>显示敌人</value>
</data>
<data name="Loc_IsPlayersTabEnemiesVisible_Description" xml:space="preserve">
<value>在玩家列表中显示敌人。</value>
</data>
<data name="Loc_IsPlayersTabOthersVisible" xml:space="preserve">
<value>显示其他人</value>
</data>
<data name="Loc_IsPlayersTabOthersVisible_Description" xml:space="preserve">
<value>在玩家列表中显示其他人。</value>
</data>
<data name="Loc_IsTextColorAppliedToChatName" xml:space="preserve">
<value>对聊天名称应用颜色</value>
</data>
<data name="Loc_IsTextColorAppliedToChatName_Description" xml:space="preserve">
<value>颜色是否将应用于聊天中的名称。</value>
</data>
<data name="Loc_IsTextColorAppliedToNameplateName" xml:space="preserve">
<value>将颜色应用于铭牌名称</value>
</data>
<data name="Loc_IsTextColorAppliedToNameplateName_Description" xml:space="preserve">
<value>颜色是否应用于铭牌中的名称。</value>
</data>
<data name="Loc_IsTextColorAppliedToNameplateTitle" xml:space="preserve">
<value>将颜色应用于铭牌标题</value>
</data>
<data name="Loc_IsTextColorAppliedToNameplateTitle_Description" xml:space="preserve">
<value>颜色是否应用于铭牌中的标题。</value>
</data>
<data name="Loc_IsTextColorAppliedToNameplateFreeCompany" xml:space="preserve">
<value>将颜色应用于铭牌自由公司</value>
</data>
<data name="Loc_IsTextColorAppliedToNameplateFreeCompany_Description" xml:space="preserve">
<value>颜色是否应用于铭牌中的自由公司。</value>
</data>
<data name="Loc_ActivityContextSelection_All" xml:space="preserve">
<value>任何位置</value>
</data>
<data name="Loc_ActivityContextSelection_None" xml:space="preserve">
<value>无任务</value>
</data>
<data name="Loc_ActivityContextSelection_PveDuty" xml:space="preserve">
<value>PvE 任务</value>
</data>
<data name="Loc_ActivityContextSelection_PvpDuty" xml:space="preserve">
<value>PvP 任务</value>
</data>
<data name="Loc_Static_CurrentActivityProfile" xml:space="preserve">
<value>当前活动个人资料</value>
</data>
<data name="Loc_ActivityContextSelection" xml:space="preserve">
<value>以下选项将被应用于</value>
</data>
<data name="Loc_ActivityContextSelection_All_Description" xml:space="preserve">
<value>下面的大多数选项将适用于您所在的任何地方。 在主世界、PvE 任务和 PvP 任务中,它们将是相同的。 您专门为另一个上下文定义的选项将被覆盖。</value>
</data>
<data name="Loc_ActivityContextSelection_None_Description" xml:space="preserve">
<value>以下大多数选项仅在您不在任何任务范围内时才适用。</value>
</data>
<data name="Loc_ActivityContextSelection_PveDuty_Description" xml:space="preserve">
<value>以下大多数选项仅适用于 PvE 任务。</value>
</data>
<data name="Loc_ActivityContextSelection_PvpDuty_Description" xml:space="preserve">
<value>以下大多数选项仅适用于 PvP 任务。</value>
</data>
<data name="Loc_Static_ChatExperimental" xml:space="preserve">
<value>聊天(实验性)</value>
</data>
<data name="Loc_Static_OtherExperimental" xml:space="preserve">
<value>其他(实验性)</value>
</data>
<data name="Loc_InsertBehindNumberPrefixInChat" xml:space="preserve">
<value>在聊天中插入组编号前缀</value>
</data>
<data name="Loc_InsertBehindNumberPrefixInChat_Description" xml:space="preserve">
<value>如果启用,标签和图标将插入聊天中组/联盟号码前缀的后面,而不是前面。 </value>
</data>
<data name="Loc_NameplateCategory" xml:space="preserve">
<value>铭牌属性</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Description" xml:space="preserve">
<value>定义铭牌的免费公司元素何时可见或不可见。</value>
</data>
<data name="Loc_NameplateTitlePosition_Description" xml:space="preserve">
<value>定义铭牌标题元素的位置。它应该在名称上方还是下方? 你来决定!</value>
</data>
<data name="Loc_NameplateTitleVisibility_Description" xml:space="preserve">
<value>定义铭牌的标题元素何时可见或不可见。</value>
</data>
<data name="Loc_Static_Chat" xml:space="preserve">
<value>聊天</value>
</data>
<data name="Loc_IsRoleJobIconVisibleInNameplates" xml:space="preserve">
<value>在铭牌上显示角色图标</value>
</data>
<data name="Loc_IsJobIconVisibleInNameplates" xml:space="preserve">
<value>在铭牌上显示职业图标</value>
</data>
<data name="Loc_IsJobIconVisibleInNameplates_Description" xml:space="preserve">
<value>职业图标是否显示在铭牌上。</value>
</data>
<data name="Loc_JobIconSet" xml:space="preserve">
<value>职业图标集</value>
</data>
<data name="Loc_JobIconSet_Description" xml:space="preserve">
<value>用于显示职业图标的图标集。您也可以选择角色图标集来代替显示角色图标。</value>
</data>
<data name="Loc_ChatFeatureCategory" xml:space="preserve">
<value>高级聊天选项</value>
</data>
<data name="Loc_TargetChatTypes" xml:space="preserve">
<value>目标聊天类型</value>
</data>
<data name="Loc_TargetChatTypes_Description" xml:space="preserve">
<value>定义应为哪种聊天类型启用此标签的聊天功能。</value>
</data>
<data name="Loc_Static_StatusIconPrioList" xml:space="preserve">
<value>状态图标优先级</value>
</data>
<data name="Loc_StatusIconPriorizerConditionSets_InDuty" xml:space="preserve">
<value>在任中</value>
</data>
<data name="Loc_StatusIconPriorizerConditionSets_InDuty_Description" xml:space="preserve">
<value>在任务中应优先考虑的状态图标。</value>
</data>
<data name="Loc_StatusIconPriorizerConditionSets_InForay" xml:space="preserve">
<value>突袭中</value>
</data>
<data name="Loc_StatusIconPriorizerConditionSets_InForay_Description" xml:space="preserve">
<value>在突袭中应优先考虑的状态图标。</value>
</data>
<data name="Loc_StatusIconPriorizerConditionSets_Overworld" xml:space="preserve">
<value>主世界</value>
</data>
<data name="Loc_StatusIconPriorizerConditionSets_Overworld_Description" xml:space="preserve">
<value>应该在主世界范围内优先考虑的状态图标。</value>
</data>
<data name="Loc_StatusIconPriorizer_ResetToDefault" xml:space="preserve">
<value>重置为默认</value>
</data>
<data name="Loc_StatusIconPriorizer_ResetToDefault_Description" xml:space="preserve">
<value>将所有条件集重置为默认设置</value>
</data>
<data name="Loc_StatusIconPriorizer_ResetToEmpty" xml:space="preserve">
<value>重置为空</value>
</data>
<data name="Loc_StatusIconPriorizer_ResetToEmpty_Description" xml:space="preserve">
<value>将所有条件集清除为空集合。 没有状态图标将被优先考虑。</value>
</data>
<data name="Loc_UsePriorizedIcons" xml:space="preserve">
<value>使用优先图标</value>
</data>
<data name="Loc_UsePriorizedIcons_Description" xml:space="preserve">
<value>如果您启用了职业图标,则选中此选项将强制一组状态图标优先于职业图标。
禁用时,只有断开连接状态图标优先。</value>
</data>
<data name="Loc_StatusIcons_Busy" xml:space="preserve">
<value>忙碌</value>
</data>
<data name="Loc_StatusIcons_Disconnecting" xml:space="preserve">
<value>正在断开连接</value>
</data>
<data name="Loc_StatusIcons_DutyFinder" xml:space="preserve">
<value>任务查找器</value>
</data>
<data name="Loc_StatusIcons_GroupPose" xml:space="preserve">
<value>集体姿势</value>
</data>
<data name="Loc_StatusIcons_Idle" xml:space="preserve">
<value>空闲</value>
</data>
<data name="Loc_StatusIcons_InDuty" xml:space="preserve">
<value>在任中</value>
</data>
<data name="Loc_StatusIcons_Mentor" xml:space="preserve">
<value>导师</value>
</data>
<data name="Loc_StatusIcons_MentorCrafting" xml:space="preserve">
<value>导师制作</value>
</data>
<data name="Loc_StatusIcons_MentorPvE" xml:space="preserve">
<value>PvE 导师</value>
</data>
<data name="Loc_StatusIcons_MentorPvP" xml:space="preserve">
<value>PvP 导师</value>
</data>
<data name="Loc_StatusIcons_NewAdventurer" xml:space="preserve">
<value>新冒险家</value>
</data>
<data name="Loc_StatusIcons_PartyLeader" xml:space="preserve">
<value>队长</value>
</data>
<data name="Loc_StatusIcons_PartyMember" xml:space="preserve">
<value>队员</value>
</data>
<data name="Loc_StatusIcons_Returner" xml:space="preserve">
<value>归还者</value>
</data>
<data name="Loc_StatusIcons_RolePlaying" xml:space="preserve">
<value>角色扮演</value>
</data>
<data name="Loc_StatusIcons_ViewingCutscene" xml:space="preserve">
<value>观看过场动画</value>
</data>
<data name="Loc_DefaultPluginDataTemplate" xml:space="preserve">
<value>模板</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_Basic" xml:space="preserve">
<value>基本</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_Basic_Description" xml:space="preserve">
<value>与空的相同,但包含一个非常基本的格式和着色预配置。
如果您想进行自己的配置但需要基本格式,也可以使用。</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_Description" xml:space="preserve">
<value>您可以在此处为以下所有设置选择模板。
该模板用作基本属性集。您所做的每项更改都会被保存。但不会保存此模板中的所有选项。
这可以帮助您不需要覆盖那么多属性 - 或者通过在没有模板的情况下进行完全自己的配置。
警告:
更改此项可能会导致属性被重置。您可能会丢失部分配置。
更改此设置后,请确保一切都按照您喜欢的方式进行设置。</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_Full" xml:space="preserve">
<value>已满</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_Full_Description" xml:space="preserve">
<value>用颜色显示工作标签,并为玩家名称元素着色。</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_None" xml:space="preserve">
<value>空空如也</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_None_Description" xml:space="preserve">
<value>没有进行单一配置。 这是一个完全空的模板。
如果您想控制每个选项或只想进行一些配置,请使用此选项。</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_Simple" xml:space="preserve">
<value>简易</value>
</data>
<data name="Loc_DefaultPluginDataTemplate_Simple_Description" xml:space="preserve">
<value>通过替换标题显示带有颜色的职业标签和角色图标。</value>
</data>
<data name="Loc_DeadPlayerHandling_GrayOut" xml:space="preserve">
<value>变灰</value>
</data>
<data name="Loc_DeadPlayerHandling_Ignore" xml:space="preserve">
<value>忽略</value>
</data>
<data name="Loc_DeadPlayerHandling_Include" xml:space="preserve">
<value>包含</value>
</data>
<data name="Loc_DeadPlayerHandling" xml:space="preserve">
<value>处理死亡玩家</value>
</data>
<data name="Loc_DeadPlayerHandling_GrayOut_Description" xml:space="preserve">
<value>应用任何标签,但将铭牌变灰。</value>
</data>
<data name="Loc_DeadPlayerHandling_Ignore_Description" xml:space="preserve">
<value>不要处理死亡玩家,不要应用任何标签。</value>
</data>
<data name="Loc_DeadPlayerHandling_Include_Description" xml:space="preserve">
<value>在他们还活着的时候处理死去的玩家。 死了和活着的玩家没有区别。</value>
</data>
<data name="Loc_MoveStatusIconToNameplateTextIfPossible" xml:space="preserve">
<value>如果可以,将状态图标移动到铭牌文本</value>
</data>
<data name="Loc_MoveStatusIconToNameplateTextIfPossible_Description" xml:space="preserve">
<value>如果当前状态图标可用作字体图标,则将其移动到铭牌中的玩家姓名文本,这样就有地方可以使用另一个图标,例如职业图标。</value>
</data>
<data name="Loc_IsRoleIconVisibleInChat" xml:space="preserve">
<value>在聊天中显示角色图标</value>
</data>
<data name="Loc_TargetChatTypesIncludeUndefined" xml:space="preserve">
<value>包含未定义的聊天类型</value>
</data>
<data name="Loc_TargetChatTypesIncludeUndefined_Description" xml:space="preserve">
<value>启用后,标签​​将应用于所有未定义类型的聊天消息。
如果游戏更新并且所有聊天类型的枚举由于值偏移而变得无效,或者插件出于任何原因创建自定义聊天类型,就会发生这种情况。</value>
</data>
<data name="Loc_Command_playertags_v2" xml:space="preserve">
<value>显示玩家标签的配置窗口
子命令:
enableglobal on|of|toggle -&gt; 设置一个全局主开关,在不更改当前配置的情况下启用或禁用所有插件功能。</value>
</data>
</root>

Some files were not shown because too many files have changed in this diff Show More