Initial checkin

This commit is contained in:
r00telement
2021-10-04 22:57:54 +01:00
parent 6854309ea4
commit 0a6d6c66cf
31 changed files with 8723 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.vs/
obj/
bin/
*.user

25
PlayerTags.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayerTags", "PlayerTags\PlayerTags.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,7 @@
namespace PlayerTags.Config
{
public class CustomColorConfig
{
public ushort? Id = null;
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Linq;
namespace PlayerTags.Config
{
[Serializable]
public class CustomTagConfig : TagConfig
{
public CustomColorConfig CustomColor = new CustomColorConfig();
public string Name = "";
public string FormattedGameObjectNames = "";
private string[] GameObjectNames
{
get
{
return FormattedGameObjectNames.Split(';').Select(gameObjectName => gameObjectName.ToLower().Trim()).ToArray();
}
}
public bool IncludesGameObjectName(string gameObjectName)
{
return GameObjectNames.Contains(gameObjectName);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace PlayerTags.Config
{
public enum FreeCompanyNameplateVisibility
{
Default,
Never
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace PlayerTags.Config
{
[Serializable]
public class JobOverrideConfig
{
public CustomColorConfig CustomColor = new CustomColorConfig();
}
}

View File

@@ -0,0 +1,73 @@
using Dalamud.Configuration;
using Dalamud.Data;
using Dalamud.Plugin;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Config
{
[Serializable]
public class MainConfig : IPluginConfiguration
{
public static Dictionary<byte, Role> RolesById { get; } = new Dictionary<byte, Role>()
{
{ 0, Role.LandHand },
{ 1, Role.Tank },
{ 2, Role.DPS },
{ 3, Role.DPS },
{ 4, Role.Healer },
};
public int Version { get; set; } = 0;
public FreeCompanyNameplateVisibility FreeCompanyVisibility = FreeCompanyNameplateVisibility.Default;
public TitleNameplateVisibility TitleVisibility = TitleNameplateVisibility.Default;
public TitleNameplatePosition TitlePosition = TitleNameplatePosition.Default;
public RoleTagConfig RoleTag = new RoleTagConfig();
public List<CustomTagConfig> CustomTagConfigs = new List<CustomTagConfig>();
public bool IsPlayerNameRandomlyGenerated = false;
[NonSerialized]
private DalamudPluginInterface? m_PluginInterface;
public event System.Action? Saved;
public void Initialize(DalamudPluginInterface pluginInterface, DataManager dataManager)
{
m_PluginInterface = pluginInterface;
// Populate each role config with all of its jobs if they aren't already in it
foreach (var roleConfigPair in RoleTag.RoleOverrideConfigs)
{
var role = roleConfigPair.Key;
var roleConfig = roleConfigPair.Value;
var classJobs = dataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => RolesById[classJob.Role] == role))
{
if (!roleConfig.JobOverrideConfigs.ContainsKey(classJob.Abbreviation))
{
roleConfig.JobOverrideConfigs[classJob.Abbreviation] = new JobOverrideConfig();
}
}
}
}
}
public void Save()
{
if (m_PluginInterface != null)
{
m_PluginInterface.SavePluginConfig(this);
Saved?.Invoke();
};
}
}
}

View File

@@ -0,0 +1,403 @@
using Dalamud.Interface.Components;
using Dalamud.Logging;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using PlayerTags.Resources;
using System;
using System.Linq;
using System.Numerics;
namespace PlayerTags.Config
{
public class MainConfigUI
{
private MainConfig m_Config;
private bool m_OpenPopupRequested;
private CustomColorConfig? m_ColorPickerPopupDataContext;
private bool m_IsVisible = false;
public bool IsVisible
{
get { return m_IsVisible; }
set { m_IsVisible = value; }
}
public MainConfigUI(MainConfig config)
{
m_Config = config;
}
private string GetLocString<TEnum>(bool isDescription)
where TEnum : Enum
{
return GetLocString(typeof(TEnum).Name, isDescription);
}
private string GetLocString<TEnum>(TEnum enumValue, bool isDescription)
where TEnum : Enum
{
return GetLocString($"{typeof(TEnum).Name}_{enumValue}", isDescription);
}
private string GetLocString(string locStringId, bool isDescription)
{
string completeLocStringId = $"Loc_{locStringId}";
if (isDescription)
{
completeLocStringId += "_Description";
}
return GetLocString(completeLocStringId);
}
private string GetLocString(string completeLocStringId)
{
string? value = Strings.ResourceManager.GetString(completeLocStringId, Strings.Culture);
if (value != null)
{
return value;
}
PluginLog.Error($"Failed to get localized string for id {completeLocStringId}");
return completeLocStringId;
}
public void Draw()
{
if (m_Config == null || !IsVisible)
{
return;
}
if (ImGui.Begin(Strings.Loc_Static_PluginName, ref m_IsVisible))
{
if (ImGui.BeginTabBar("MainTabBar"))
{
if (ImGui.BeginTabItem(Strings.Loc_Static_General))
{
DrawHeading(Strings.Loc_Static_Nameplates);
DrawEnumComboBox(
ref m_Config.FreeCompanyVisibility,
() => m_Config.Save());
DrawEnumComboBox(
ref m_Config.TitleVisibility,
() => m_Config.Save());
DrawEnumComboBox(
ref m_Config.TitlePosition,
() => m_Config.Save());
DrawHeading(Strings.Loc_Static_Development);
DrawCheckbox(
nameof(m_Config.IsPlayerNameRandomlyGenerated),
ref m_Config.IsPlayerNameRandomlyGenerated,
() => m_Config.Save());
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem(Strings.Loc_Static_RoleAndJobTags))
{
DrawEnumComboBox(
ref m_Config.RoleTag.Format,
() => m_Config.Save());
DrawTagConfig(m_Config.RoleTag);
DrawHeading(Strings.Loc_Static_Roles);
if (ImGui.BeginTabBar("JobAndRolesTabBar"))
{
foreach (var rolePair in m_Config.RoleTag.RoleOverrideConfigs)
{
var role = rolePair.Key;
var roleConfig = rolePair.Value;
if (ImGui.BeginTabItem(GetLocString($"{role.GetType().Name}_{role}", false)))
{
DrawCheckbox(
$"{roleConfig.GetType().Name}_{nameof(roleConfig.IsEnabled)}",
ref roleConfig.IsEnabled,
() => m_Config.Save());
DrawTextBox(
$"{roleConfig.GetType().Name}_{nameof(roleConfig.Name)}",
ref roleConfig.Name,
() => m_Config.Save());
DrawOptionalCustomColor(
$"{roleConfig.CustomColor.GetType().Name}_IsEnabled",
roleConfig.CustomColor.Id.ToString()!,
roleConfig.CustomColor);
DrawHeading(Strings.Loc_Static_Jobs);
foreach (var key in roleConfig.JobOverrideConfigs.Keys.OrderBy(key => key))
{
if (string.IsNullOrEmpty(key))
{
continue;
}
JobOverrideConfig jobConfig = roleConfig.JobOverrideConfigs[key];
ImGui.Columns(2, "columns", false);
ImGui.SetColumnWidth(0, 42);
ImGui.Text(key);
ImGui.NextColumn();
DrawOptionalCustomColor(
$"{roleConfig.CustomColor.GetType().Name}_IsEnabled",
key,
jobConfig.CustomColor);
ImGui.Columns();
}
ImGui.EndTabItem();
}
}
ImGui.EndTabBar();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem(Strings.Loc_Static_CustomTags))
{
if (ImGui.Button(Strings.Loc_Static_AddCustomTag))
{
m_Config.CustomTagConfigs.Add(new CustomTagConfig());
m_Config.Save();
}
if (!m_Config.CustomTagConfigs.Any())
{
ImGui.Text(Strings.Loc_Static_NoCustomTagsAdded);
}
else
{
foreach (var customTagConfig in m_Config.CustomTagConfigs.ToArray())
{
ImGui.PushID(customTagConfig.GetHashCode().ToString());
ImGui.Separator();
DrawTextBox(
$"{customTagConfig.GetType().Name}_{nameof(customTagConfig.Name)}",
ref customTagConfig.Name,
() => { m_Config.Save(); });
DrawOptionalCustomColor(
$"{customTagConfig.CustomColor.GetType().Name}_IsEnabled",
customTagConfig.CustomColor.Id.ToString()!,
customTagConfig.CustomColor);
DrawTextBox(
$"{customTagConfig.GetType().Name}_{nameof(customTagConfig.FormattedGameObjectNames)}",
ref customTagConfig.FormattedGameObjectNames,
() => { m_Config.Save(); });
DrawTagConfig(customTagConfig);
ImGui.Spacing();
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0.4f, 0.1f, 0.1f, 1));
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.6f, 0.2f, 0.2f, 1));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.6f, 0.2f, 0.2f, 1));
if (ImGui.Button(Strings.Loc_Static_RemoveCustomTag))
{
m_Config.CustomTagConfigs.Remove(customTagConfig);
m_Config.Save();
}
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.PopID();
}
}
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
if (m_OpenPopupRequested == true)
{
m_OpenPopupRequested = false;
ImGui.OpenPopup("ColorPickerPopup");
}
ImGui.SetNextWindowSize(new Vector2(400, 284));
if (ImGui.BeginPopup("ColorPickerPopup"))
{
DrawUIColorPicker(
(UIColor value) =>
{
if (m_ColorPickerPopupDataContext != null)
{
m_ColorPickerPopupDataContext.Id = (ushort)value.RowId;
m_ColorPickerPopupDataContext = null;
m_Config.Save();
}
ImGui.CloseCurrentPopup();
});
ImGui.EndPopup();
}
ImGui.End();
}
}
private void DrawTagConfig(TagConfig tagConfig)
{
if (m_Config == null || !IsVisible)
{
return;
}
DrawHeading(Strings.Loc_Static_ChatTag);
ImGui.PushID("Chat");
DrawEnumComboBox(
ref tagConfig.ChatPosition,
() => m_Config.Save());
ImGui.PopID();
DrawHeading(Strings.Loc_Static_NameplateTag);
ImGui.PushID("Nameplate");
DrawEnumComboBox(
ref tagConfig.NameplatePosition,
() => m_Config.Save());
DrawEnumComboBox(
ref tagConfig.NameplateElement,
() => m_Config.Save());
ImGui.PopID();
}
private void DrawOptionalCustomColor(string locStringId, string colorId, CustomColorConfig customColorConfig)
{
if (customColorConfig.Id.HasValue)
{
DrawColorButton(
colorId,
UIColorHelper.ToColor(customColorConfig.Id.Value),
() =>
{
m_ColorPickerPopupDataContext = customColorConfig;
m_OpenPopupRequested = true;
});
ImGui.SameLine();
}
var isChecked = customColorConfig.Id != null;
DrawCheckbox(
locStringId,
ref isChecked,
() =>
{
if (!isChecked)
{
customColorConfig.Id = null;
m_Config.Save();
}
else
{
customColorConfig.Id = (ushort)UIColorHelper.UIColors.First().RowId;
m_Config.Save();
}
});
}
private void DrawSeparator()
{
ImGui.Spacing();
ImGui.Spacing();
ImGui.Spacing();
ImGui.Spacing();
}
private void DrawHeading(string label)
{
ImGui.TextColored(new Vector4(0.7f, 0.6f, 1f, 1f), label);
}
private void DrawEnumComboBox<TEnum>(ref TEnum currentValue, System.Action changed)
where TEnum : Enum
{
ImGui.Text(GetLocString<TEnum>(false));
ImGuiComponents.HelpMarker(GetLocString<TEnum>(true));
if (ImGui.BeginCombo($"###{currentValue.GetType().Name}", GetLocString(currentValue, false)))
{
foreach (string enumValueString in typeof(TEnum).GetEnumNames())
{
TEnum enumValue = (TEnum)Enum.Parse(typeof(TEnum), enumValueString);
bool isSelected = enumValueString == currentValue.ToString();
if (ImGui.Selectable($"{GetLocString(enumValue, false)}###{enumValueString}", isSelected))
{
currentValue = (TEnum)Enum.Parse(typeof(TEnum), enumValueString);
ImGui.SetItemDefaultFocus();
changed();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(GetLocString(enumValue, true));
}
}
ImGui.EndCombo();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(GetLocString(currentValue, true));
}
}
private void DrawCheckbox(string locStringId, ref bool isChecked, System.Action changed)
{
if (ImGui.Checkbox(GetLocString(locStringId, false), ref isChecked))
{
changed();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(GetLocString(locStringId, true));
}
}
private void DrawColorButton(string colorId, Vector4 color, System.Action clicked)
{
if (ImGui.ColorButton(colorId, color))
{
clicked();
}
}
private void DrawTextBox(string locStringId, ref string text, System.Action changed)
{
ImGui.Text(GetLocString(locStringId, false));
ImGuiComponents.HelpMarker(GetLocString(locStringId, true));
var oldText = text;
ImGui.InputText($"###{locStringId}", ref text, 1024);
if (text != oldText)
{
changed();
}
}
private void DrawUIColorPicker(System.Action<UIColor> clicked)
{
ImGui.PushID(clicked.GetHashCode());
ImGui.Columns(12, "columns", false);
foreach (var uiColor in UIColorHelper.UIColors)
{
DrawColorButton(
uiColor.RowId.ToString(),
UIColorHelper.ToColor(uiColor),
() =>
{
clicked(uiColor);
});
ImGui.NextColumn();
}
ImGui.Columns();
ImGui.PopID();
}
}
}

View File

@@ -0,0 +1,9 @@
namespace PlayerTags.Config
{
public enum NameplateElement
{
Name,
Title,
FreeCompany
}
}

10
PlayerTags/Config/Role.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace PlayerTags.Config
{
public enum Role
{
LandHand,
Tank,
Healer,
DPS
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
namespace PlayerTags.Config
{
[Serializable]
public class RoleOverrideConfig
{
public bool IsEnabled = true;
public string Name = "";
public CustomColorConfig CustomColor = new CustomColorConfig();
public Dictionary<string, JobOverrideConfig> JobOverrideConfigs = new Dictionary<string, JobOverrideConfig>();
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace PlayerTags.Config
{
[Serializable]
public class RoleTagConfig : TagConfig
{
public RoleTagFormat Format = RoleTagFormat.AbbreviatedJobName;
public Dictionary<Role, RoleOverrideConfig> RoleOverrideConfigs = new Dictionary<Role, RoleOverrideConfig>()
{
{ Role.LandHand, new RoleOverrideConfig() { IsEnabled = true, Name = "Land/Hand", CustomColor = new CustomColorConfig() { Id = 3 } } },
{ Role.Tank, new RoleOverrideConfig() { IsEnabled = true, Name = "Tank", CustomColor = new CustomColorConfig() { Id = 542 } } },
{ Role.Healer, new RoleOverrideConfig() { IsEnabled = true, Name = "Healer", CustomColor = new CustomColorConfig() { Id = 45 } } },
{ Role.DPS, new RoleOverrideConfig() { IsEnabled = true, Name = "DPS", CustomColor = new CustomColorConfig() { Id = 511 } } },
};
}
}

View File

@@ -0,0 +1,9 @@
namespace PlayerTags.Config
{
public enum RoleTagFormat
{
AbbreviatedJobName,
JobName,
RoleName
}
}

View File

@@ -0,0 +1,10 @@
namespace PlayerTags.Config
{
public enum StringPosition
{
None,
Before,
After,
Replace
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace PlayerTags.Config
{
[Serializable]
public class TagConfig
{
public NameplateElement NameplateElement = NameplateElement.Name;
public StringPosition NameplatePosition = StringPosition.Before;
public StringPosition ChatPosition = StringPosition.Before;
}
}

View File

@@ -0,0 +1,9 @@
namespace PlayerTags.Config
{
public enum TitleNameplatePosition
{
Default,
AlwaysAboveName,
AlwaysBelowName
}
}

View File

@@ -0,0 +1,10 @@
namespace PlayerTags.Config
{
public enum TitleNameplateVisibility
{
Default,
Always,
Never,
WhenHasTags
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<DalamudPackager
ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
MakeZip="true"
Exclude="PlayerTags.pdb;PlayerTags.deps.json"
/>
</Target>
</Project>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>r00telement</Authors>
<Version>1.0.0.0</Version>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net5.0-windows7.0</TargetFramework>
<Platforms>x64</Platforms>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>$(AppData)\XIVLauncher\devPlugins\PlayerTags\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.2" />
<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>
<Compile Update="Resources\Strings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Strings.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Strings.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Resources\Words\Adjectives.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\Words\Nouns.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
{
"Author": "r00telement",
"Name": "Player Tags",
"Description": "See tags for players in nameplates and chat, such as their role and job, or custom tags for players you know.",
"RepoUrl": "https://github.com/r00telement/PlayerTags",
"IconUrl": "https://github.com/r00telement/PlayerTags/master/Resources/Chat_1.png",
"ImageUrls": [
"https://github.com/r00telement/PlayerTags/master/Resources/Chat_1.png",
"https://github.com/r00telement/PlayerTags/master/Resources/Nameplates_1.png"
]
}

619
PlayerTags/Plugin.cs Normal file
View File

@@ -0,0 +1,619 @@
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.IoC;
using Dalamud.Logging;
using Dalamud.Plugin;
using PlayerTags.Config;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags
{
public sealed class Plugin : IDalamudPlugin
{
public string Name => "Player Tags";
private const string c_CommandName = "/playertags";
[PluginService]
private static DalamudPluginInterface PluginInterface { get; set; } = null!;
[PluginService]
private static Framework Framework { get; set; } = null!;
[PluginService]
private static ChatGui ChatGui { get; set; } = null!;
[PluginService]
private static GameGui GameGui { get; set; } = null!;
[PluginService]
private static ObjectTable ObjectTable { get; set; } = null!;
[PluginService]
private static DataManager DataManager { get; set; } = null!;
[PluginService]
private static CommandManager CommandManager { get; set; } = null!;
private MainConfig m_Config;
private MainConfigUI m_ConfigUI;
private Dictionary<string, Payload[]> m_JobTagPayloads = new Dictionary<string, Payload[]>();
private Dictionary<CustomTagConfig, Payload[]> m_CustomTagPayloads = new Dictionary<CustomTagConfig, Payload[]>();
private TextPayload m_SpaceTextPayload = new TextPayload($" ");
private PluginHooks m_PluginHooks;
private RandomNameGenerator? m_RandomNameGenerator = null;
public Plugin()
{
UIColorHelper.Initialize(DataManager);
m_Config = PluginInterface.GetPluginConfig() as MainConfig ?? new MainConfig();
m_Config.Initialize(PluginInterface, DataManager);
m_Config.Saved += Configuration_Saved;
m_ConfigUI = new MainConfigUI(m_Config);
CommandManager.AddHandler(c_CommandName, new CommandInfo((string command, string arguments) =>
{
m_ConfigUI.IsVisible = true;
})
{
HelpMessage = "Shows the config"
});
PluginInterface.UiBuilder.Draw += UiBuilder_Draw;
PluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
m_PluginHooks = new PluginHooks(Framework, ObjectTable, GameGui, SetNameplate);
ChatGui.ChatMessage += Chat_ChatMessage;
if (m_Config.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator == null)
{
m_RandomNameGenerator = new RandomNameGenerator();
}
}
public void Dispose()
{
PluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
CommandManager.RemoveHandler(c_CommandName);
ChatGui.ChatMessage -= Chat_ChatMessage;
m_Config.Saved -= Configuration_Saved;
m_PluginHooks?.Dispose();
}
private void Configuration_Saved()
{
// Invalidate the cached payloads so they get remade
m_JobTagPayloads.Clear();
m_CustomTagPayloads.Clear();
if (m_Config.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator == null)
{
m_RandomNameGenerator = new RandomNameGenerator();
}
}
private void UiBuilder_Draw()
{
m_ConfigUI.Draw();
}
private void UiBuilder_OpenConfigUi()
{
m_ConfigUI.IsVisible = true;
}
private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
{
AddTagsToChat(sender, out _);
AddTagsToChat(message, out _);
}
/// <summary>
/// Sets the strings on a nameplate.
/// </summary>
/// <param name="gameObject">The game object context.</param>
/// <param name="name">The name text.</param>
/// <param name="title">The title text.</param>
/// <param name="freeCompany">The free company text.</param>
/// <param name="isTitleVisible">Whether the title is visible.</param>
/// <param name="isTitleAboveName">Whether the title is above the name or below it.</param>
/// <param name="iconId">The icon id.</param>
/// <param name="isNameChanged">Whether the name was changed.</param>
/// <param name="isTitleChanged">Whether the title was changed.</param>
/// <param name="isFreeCompanyChanged">Whether the free company was changed.</param>
private void SetNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, ref bool isTitleVisible, ref bool isTitleAboveName, ref int iconId, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged)
{
AddTagsToNameplate(gameObject, name, title, freeCompany, out isNameChanged, out isTitleChanged, out isFreeCompanyChanged);
if (m_Config.TitlePosition == TitleNameplatePosition.AlwaysAboveName)
{
isTitleAboveName = true;
}
else if (m_Config.TitlePosition == TitleNameplatePosition.AlwaysBelowName)
{
isTitleAboveName = false;
}
if (m_Config.TitleVisibility == TitleNameplateVisibility.Default)
{
}
else if (m_Config.TitleVisibility == TitleNameplateVisibility.Always)
{
isTitleVisible = true;
}
else if (m_Config.TitleVisibility == TitleNameplateVisibility.Never)
{
isTitleVisible = false;
}
else if (m_Config.TitleVisibility == TitleNameplateVisibility.WhenHasTags)
{
isTitleVisible = isTitleChanged;
}
if (m_Config.FreeCompanyVisibility == FreeCompanyNameplateVisibility.Default)
{
}
else if (m_Config.FreeCompanyVisibility == FreeCompanyNameplateVisibility.Never)
{
freeCompany.Payloads.Clear();
isFreeCompanyChanged = true;
}
}
/// <summary>
/// Gets the job tag payloads for the given character. If the payloads don't yet exist then they are created.
/// </summary>
/// <param name="character">The character to get job tag payloads for.</param>
/// <returns>A list of job tag payloads for the given character.</returns>
private IEnumerable<Payload> GetJobTagPayloads(Character character)
{
var roleId = character.ClassJob.GameData.Role;
var jobAbbreviation = character.ClassJob.GameData.Abbreviation;
var role = MainConfig.RolesById[roleId];
var roleConfig = m_Config.RoleTag.RoleOverrideConfigs[role];
if (!roleConfig.IsEnabled)
{
return new Payload[] { };
}
if (m_JobTagPayloads.TryGetValue(jobAbbreviation, out var payloads))
{
return payloads;
}
string text = "";
if (m_Config.RoleTag.Format == RoleTagFormat.AbbreviatedJobName)
{
text = character.ClassJob.GameData.Abbreviation;
}
else if (m_Config.RoleTag.Format == RoleTagFormat.JobName)
{
text = character.ClassJob.GameData.NameEnglish;
}
else if (m_Config.RoleTag.Format == RoleTagFormat.RoleName)
{
text = m_Config.RoleTag.RoleOverrideConfigs[role].Name;
}
List<Payload> newPayloads = new List<Payload>();
// There will always be a text payload
newPayloads.Add(new TextPayload(text));
ushort? colorId = null;
// Pick a color id if one is available
if (roleConfig.JobOverrideConfigs[jobAbbreviation].CustomColor.Id != null)
{
colorId = roleConfig.JobOverrideConfigs[jobAbbreviation].CustomColor.Id!.Value;
}
else if (roleConfig.CustomColor.Id != null)
{
colorId = roleConfig.CustomColor.Id.Value;
}
// If we picked a color id, add the payloads for it
if (colorId != null)
{
newPayloads.Insert(0, new UIForegroundPayload(colorId.Value));
newPayloads.Add(new UIForegroundPayload(0));
}
var newPayloadsArray = newPayloads.ToArray();
m_JobTagPayloads[jobAbbreviation] = newPayloadsArray;
return newPayloadsArray;
}
/// <summary>
/// Gets the payloads for the given custom tag. If the payloads don't yet exist then they are created.
/// </summary>
/// <param name="customTagConfig">The custom tag config to get payloads for.</param>
/// <returns>A list of payloads for the given custom tag.</returns>
private IEnumerable<Payload> GetCustomTagPayloads(CustomTagConfig customTagConfig)
{
if (m_CustomTagPayloads.TryGetValue(customTagConfig, out var payloads))
{
return payloads;
}
List<Payload> newPayloads = new List<Payload>();
// There will always be a text payload
newPayloads.Add(new TextPayload(customTagConfig.Name));
ushort? colorId = null;
// Pick a color id if one is available
if (customTagConfig.CustomColor.Id != null)
{
colorId = customTagConfig.CustomColor.Id!.Value;
}
// If we picked a color id, add the payloads for it
if (colorId != null)
{
newPayloads.Insert(0, new UIForegroundPayload(colorId.Value));
newPayloads.Add(new UIForegroundPayload(0));
}
var newPayloadsArray = newPayloads.ToArray();
m_CustomTagPayloads[customTagConfig] = newPayloadsArray;
return newPayloadsArray;
}
/// <summary>
/// Adds an additional space text payload in between any existing text payloads.
/// </summary>
/// <param name="payloads">The payloads to add spaces between.</param>
private void AddSpacesBetweenTextPayloads(List<Payload> payloads)
{
var textPayloads = payloads.Where(payload => payload is TextPayload).ToList();
foreach (var textPayload in textPayloads.Skip(1))
{
var index = payloads.IndexOf(textPayload);
payloads.Insert(index, m_SpaceTextPayload);
}
}
/// <summary>
/// A match found within a string.
/// </summary>
private class StringMatch
{
/// <summary>
/// The string that the match was found in.
/// </summary>
public SeString SeString { get; init; }
/// <summary>
/// The matching text payload.
/// </summary>
public TextPayload TextPayload { get; init; }
/// <summary>
/// The matching game object.
/// </summary>
public GameObject? GameObject { get; init; }
public StringMatch(SeString seString, TextPayload textPayload, GameObject? gameObject = null)
{
SeString = seString;
TextPayload = textPayload;
GameObject = gameObject;
}
/// <summary>
/// Gets the matches text.
/// </summary>
/// <returns>The match text.</returns>
public string GetMatchText()
{
if (GameObject != null)
{
return GameObject.Name.TextValue;
}
return TextPayload.Text;
}
}
/// <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 = new List<StringMatch>();
for (int payloadIndex = 0; payloadIndex < seString.Payloads.Count; ++payloadIndex)
{
var payload = seString.Payloads[payloadIndex];
if (payload is PlayerPayload playerPayload)
{
var gameObject = ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName);
// The next payload MUST be a text payload
if (payloadIndex + 1 < seString.Payloads.Count && seString.Payloads[payloadIndex + 1] is TextPayload textPayload)
{
var stringMatch = new StringMatch(seString, textPayload, gameObject);
stringMatches.Add(stringMatch);
// Don't handle the text payload twice
payloadIndex++;
}
else
{
PluginLog.Error("Expected payload after player payload to be a text payload but it wasn't");
}
}
// If it's just a text payload then either a character NEEDS to exist for it, or it needs to be identified as a character by custom tag configs
else if (payload is TextPayload textPayload)
{
var gameObject = ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == textPayload.Text);
var isIncludedInCustomTagConfig = m_Config.CustomTagConfigs.Any(customTagConfig => customTagConfig.IncludesGameObjectName(textPayload.Text));
if (gameObject != null || isIncludedInCustomTagConfig)
{
var stringMatch = new StringMatch(seString, textPayload, gameObject);
stringMatches.Add(stringMatch);
}
}
}
return stringMatches;
}
/// <summary>
/// Adds the given payload changes to the dictionary.
/// </summary>
/// <param name="stringPosition">The position of the string to add changes to.</param>
/// <param name="payloads">The payloads to add.</param>
/// <param name="stringChanges">The dictionary to add the changes to.</param>
private void AddPayloadChanges(StringPosition stringPosition, IEnumerable<Payload> payloads, Dictionary<StringPosition, List<Payload>> stringChanges)
{
if (!payloads.Any())
{
return;
}
if (!stringChanges.Keys.Contains(stringPosition))
{
stringChanges[stringPosition] = new List<Payload>();
}
stringChanges[stringPosition].AddRange(payloads);
}
/// <summary>
/// Adds the given payload changes to the dictionary.
/// </summary>
/// <param name="nameplateElement">The nameplate element to add changes to.</param>
/// <param name="stringPosition">The position of the string to add changes to.</param>
/// <param name="payloads">The payloads to add.</param>
/// <param name="nameplateChanges">The dictionary to add the changes to.</param>
private void AddPayloadChanges(NameplateElement nameplateElement, StringPosition stringPosition, IEnumerable<Payload> payloads, Dictionary<NameplateElement, Dictionary<StringPosition, List<Payload>>> nameplateChanges)
{
if (!payloads.Any())
{
return;
}
if (!nameplateChanges.Keys.Contains(nameplateElement))
{
nameplateChanges[nameplateElement] = new Dictionary<StringPosition, List<Payload>>();
}
AddPayloadChanges(stringPosition, payloads, nameplateChanges[nameplateElement]);
}
/// <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>
private void ApplyStringChanges(SeString seString, Dictionary<StringPosition, List<Payload>> stringChanges, Payload? anchorPayload = null)
{
foreach ((var stringPosition, var payloads) in stringChanges)
{
if (!payloads.Any())
{
continue;
}
AddSpacesBetweenTextPayloads(payloads);
if (stringPosition == StringPosition.Before)
{
if (anchorPayload != null)
{
var payloadIndex = seString.Payloads.IndexOf(anchorPayload);
seString.Payloads.InsertRange(payloadIndex, payloads.Append(m_SpaceTextPayload));
}
else
{
seString.Payloads.InsertRange(0, payloads.Append(m_SpaceTextPayload));
}
}
else if (stringPosition == StringPosition.After)
{
if (anchorPayload != null)
{
var payloadIndex = seString.Payloads.IndexOf(anchorPayload);
seString.Payloads.InsertRange(payloadIndex + 1, payloads.Prepend(m_SpaceTextPayload));
}
else
{
seString.Payloads.AddRange(payloads.Prepend(m_SpaceTextPayload));
}
}
else if (stringPosition == StringPosition.Replace)
{
if (anchorPayload != null)
{
var payloadIndex = seString.Payloads.IndexOf(anchorPayload);
seString.Payloads.InsertRange(payloadIndex, payloads);
seString.Payloads.Remove(anchorPayload);
}
else
{
seString.Payloads.Clear();
seString.Payloads.AddRange(payloads);
}
}
}
}
/// <summary>
/// Adds all configured 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>
/// <param name="isNameChanged">Whether the name was changed.</param>
/// <param name="isTitleChanged">Whether the title was changed.</param>
/// <param name="isFreeCompanyChanged">Whether the free company was changed.</param>
private void AddTagsToNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged)
{
isNameChanged = false;
isTitleChanged = false;
isFreeCompanyChanged = false;
Dictionary<NameplateElement, Dictionary<StringPosition, List<Payload>>> nameplateChanges = new Dictionary<NameplateElement, Dictionary<StringPosition, List<Payload>>>();
if (gameObject is Character character)
{
// Add the role tag payloads
if (m_Config.RoleTag.NameplatePosition != StringPosition.None)
{
AddPayloadChanges(m_Config.RoleTag.NameplateElement, m_Config.RoleTag.NameplatePosition, GetJobTagPayloads(character), nameplateChanges);
}
// Add randomly generated name tag payload
if (m_Config.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator != null)
{
var characterName = character.Name.TextValue;
if (characterName != null)
{
var generatedName = m_RandomNameGenerator.GetGeneratedName(characterName);
if (generatedName != null)
{
AddPayloadChanges(NameplateElement.Name, StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges);
}
}
}
}
// Add the custom tag payloads
foreach (var customTagConfig in m_Config.CustomTagConfigs)
{
if (customTagConfig.NameplatePosition != StringPosition.None && customTagConfig.FormattedGameObjectNames.Split(',').Contains(gameObject.Name.TextValue))
{
AddPayloadChanges(customTagConfig.NameplateElement, customTagConfig.NameplatePosition, GetCustomTagPayloads(customTagConfig), nameplateChanges);
}
}
// Build the final strings out of the payloads
foreach ((var nameplateElement, var stringChanges) in nameplateChanges)
{
SeString? seString = null;
if (nameplateElement == NameplateElement.Name)
{
seString = name;
isNameChanged = true;
}
else if (nameplateElement == NameplateElement.Title)
{
seString = title;
isTitleChanged = true;
}
else if (nameplateElement == NameplateElement.FreeCompany)
{
seString = freeCompany;
isFreeCompanyChanged = true;
}
if (seString != null)
{
ApplyStringChanges(seString, stringChanges);
}
}
}
/// <summary>
/// Adds all configured tags to chat.
/// </summary>
/// <param name="message">The message to change.</param>
/// <param name="isMessageChanged">Whether the message was changed.</param>
private void AddTagsToChat(SeString message, out bool isMessageChanged)
{
isMessageChanged = false;
var stringMatches = GetStringMatches(message);
foreach (var stringMatch in stringMatches)
{
Dictionary<StringPosition, List<Payload>> stringChanges = new Dictionary<StringPosition, List<Payload>>();
// The role tag payloads
if (stringMatch.GameObject is Character character)
{
if (m_Config.RoleTag.ChatPosition != StringPosition.None)
{
AddPayloadChanges(m_Config.RoleTag.ChatPosition, GetJobTagPayloads(character), stringChanges);
}
}
// Add randomly generated name tag payload
if (m_Config.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator != null)
{
var playerName = stringMatch.GetMatchText();
if (playerName != null)
{
var generatedName = m_RandomNameGenerator.GetGeneratedName(playerName);
if (generatedName != null)
{
AddPayloadChanges(StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges);
}
}
}
// Add the custom tag payloads
foreach (var customTagConfig in m_Config.CustomTagConfigs)
{
if (customTagConfig.IncludesGameObjectName(stringMatch.GetMatchText()))
{
AddPayloadChanges(customTagConfig.ChatPosition, GetCustomTagPayloads(customTagConfig), stringChanges);
}
}
ApplyStringChanges(message, stringChanges, stringMatch.TextPayload);
isMessageChanged = true;
}
}
}
}

238
PlayerTags/PluginHooks.cs Normal file
View File

@@ -0,0 +1,238 @@
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.UI;
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace PlayerTags
{
public class PluginHooks : IDisposable
{
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
private delegate IntPtr SetNameplateDelegate_Private(IntPtr nameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId);
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
private delegate IntPtr UIModule_GetRaptureAtkModuleDelegate_Private(IntPtr uiModule);
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
private delegate IntPtr Framework_GetUIModuleDelegate_Private(IntPtr framework);
private class PluginAddressResolver : BaseAddressResolver
{
private const string SetNameplateSignature = "48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 44 0F B6 E2";
internal IntPtr SetNameplatePtr;
private const string Framework_GetUIModuleSignature = "E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 92 ?? ?? ?? ?? 48 8B C8 BA ?? ?? ?? ??";
internal IntPtr Framework_GetUIModulePtr;
protected override void Setup64Bit(SigScanner scanner)
{
SetNameplatePtr = scanner.ScanText(SetNameplateSignature);
Framework_GetUIModulePtr = scanner.ScanText(Framework_GetUIModuleSignature);
}
}
private Framework m_Framework;
private ObjectTable m_ObjectTable;
private GameGui m_GameGui;
private SetNameplateDelegate m_SetNameplate;
private PluginAddressResolver m_PluginAddressResolver;
private Hook<SetNameplateDelegate_Private> m_SetNameplateHook;
private readonly Framework_GetUIModuleDelegate_Private m_GetUIModule;
private IntPtr? m_NameplateObjectArrayPtr;
private IntPtr? m_NameplateInfoArrayPtr;
public PluginHooks(Framework framework, ObjectTable objectTable, GameGui gameGui, SetNameplateDelegate setNameplate)
{
m_Framework = framework;
m_ObjectTable = objectTable;
m_GameGui = gameGui;
m_SetNameplate = setNameplate;
m_PluginAddressResolver = new PluginAddressResolver();
m_PluginAddressResolver.Setup();
m_GetUIModule = Marshal.GetDelegateForFunctionPointer<Framework_GetUIModuleDelegate_Private>(m_PluginAddressResolver.Framework_GetUIModulePtr);
m_SetNameplateHook = new Hook<SetNameplateDelegate_Private>(m_PluginAddressResolver.SetNameplatePtr, new SetNameplateDelegate_Private(SetNameplateDetour));
m_SetNameplateHook.Enable();
}
public void Dispose()
{
m_SetNameplateHook.Disable();
}
private IntPtr SetNameplateDetour(IntPtr nameplateObjectPtrOriginal, bool isTitleAboveNameOriginal, bool isTitleVisibleOriginal, IntPtr titlePtrOriginal, IntPtr namePtrOriginal, IntPtr freeCompanyPtrOriginal, int iconIdOriginal)
{
if (m_SetNameplate != null)
{
try
{
GameObject? gameObject = GetNameplateObject<GameObject>(nameplateObjectPtrOriginal);
if (gameObject != null)
{
SeString title = ReadSeString(titlePtrOriginal);
SeString name = ReadSeString(namePtrOriginal);
SeString freeCompany = ReadSeString(freeCompanyPtrOriginal);
bool isTitleVisible = isTitleVisibleOriginal;
bool isTitleAboveName = isTitleAboveNameOriginal;
int iconId = iconIdOriginal;
bool isTitleChanged;
bool isNameChanged;
bool isFreeCompanyChanged;
m_SetNameplate(gameObject, name, title, freeCompany, ref isTitleVisible, ref isTitleAboveName, ref iconId, out isNameChanged, out isTitleChanged, out isFreeCompanyChanged);
IntPtr namePtr = namePtrOriginal;
if (isNameChanged)
{
namePtr = Allocate(name);
}
IntPtr titlePtr = titlePtrOriginal;
if (isTitleChanged)
{
titlePtr = Allocate(title);
}
IntPtr freeCompanyPtr = freeCompanyPtrOriginal;
if (isFreeCompanyChanged)
{
freeCompanyPtr = Allocate(freeCompany);
}
var result = m_SetNameplateHook.Original(nameplateObjectPtrOriginal, isTitleAboveName, isTitleVisible, titlePtr, namePtr, freeCompanyPtr, iconId);
if (isNameChanged)
{
Release(ref namePtr);
}
if (isTitleChanged)
{
Release(ref titlePtr);
}
if (isFreeCompanyChanged)
{
Release(ref freeCompanyPtr);
}
return result;
}
}
catch (Exception ex)
{
PluginLog.Error(ex, $"SetNameplateDetour encountered a critical error");
}
}
return m_SetNameplateHook.Original(nameplateObjectPtrOriginal, isTitleAboveNameOriginal, isTitleVisibleOriginal, titlePtrOriginal, namePtrOriginal, freeCompanyPtrOriginal, iconIdOriginal);
}
private static SeString ReadSeString(IntPtr stringPtr)
{
return SeString.Parse(ReadStringBytes(stringPtr));
}
private static byte[] ReadStringBytes(IntPtr stringPtr)
{
if (stringPtr == IntPtr.Zero)
{
return null!;
}
var size = 0;
while (Marshal.ReadByte(stringPtr, size) != 0)
{
size++;
}
var bytes = new byte[size];
Marshal.Copy(stringPtr, bytes, 0, size);
return bytes;
}
private static IntPtr Allocate(SeString seString)
{
var bytes = seString.Encode();
IntPtr pointer = Marshal.AllocHGlobal(bytes.Length + 1);
Marshal.Copy(bytes, 0, pointer, bytes.Length);
Marshal.WriteByte(pointer, bytes.Length, 0);
return pointer;
}
private static void Release(ref IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;
}
private T? GetNameplateObject<T>(IntPtr nameplateObjectPtr)
where T : GameObject
{
if (!m_NameplateInfoArrayPtr.HasValue)
{
// Get the nameplate object array
var namePlateAddonPtr = m_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!;
}
m_NameplateObjectArrayPtr = nameplateObjectArrayPtr;
// Get the nameplate info
IntPtr raptureAtkModulePtr;
var frameworkPtr = m_Framework.Address.BaseAddress;
var uiModulePtr = m_GetUIModule(frameworkPtr);
unsafe
{
var uiModule = *(UIModule*)uiModulePtr;
var UIModule_GetRaptureAtkModuleAddress = new IntPtr(uiModule.vfunc[7]);
var GetRaptureAtkModule = Marshal.GetDelegateForFunctionPointer<UIModule_GetRaptureAtkModuleDelegate_Private>(UIModule_GetRaptureAtkModuleAddress);
raptureAtkModulePtr = GetRaptureAtkModule(uiModulePtr);
}
if (raptureAtkModulePtr == IntPtr.Zero)
{
return null!;
}
m_NameplateInfoArrayPtr = raptureAtkModulePtr + Marshal.OffsetOf(typeof(RaptureAtkModule), nameof(RaptureAtkModule.NamePlateInfoArray)).ToInt32();
}
// Determine the index of this nameplate
var namePlateObjectSize = Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject));
var namePlateObjectPtr0 = m_NameplateObjectArrayPtr!.Value + namePlateObjectSize * 0;
var namePlateIndex = (nameplateObjectPtr.ToInt64() - namePlateObjectPtr0.ToInt64()) / namePlateObjectSize;
if (namePlateIndex < 0 || namePlateIndex >= 50)
{
return null!;
}
var namePlateInfoPtr = new IntPtr(m_NameplateInfoArrayPtr.Value.ToInt64() + Marshal.SizeOf(typeof(RaptureAtkModule.NamePlateInfo)) * namePlateIndex);
RaptureAtkModule.NamePlateInfo namePlateInfo = Marshal.PtrToStructure<RaptureAtkModule.NamePlateInfo>(namePlateInfoPtr);
// Get the player character for this nameplate info
var objectId = namePlateInfo.ObjectID.ObjectID;
T? gameObject = m_ObjectTable.FirstOrDefault(obj => obj.ObjectId == objectId) as T;
if (gameObject == null)
{
return null!;
}
return gameObject!;
}
}
}

View File

@@ -0,0 +1,96 @@
using Dalamud.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
namespace PlayerTags
{
/// <summary>
/// Generates names based on an existing list of words.
/// </summary>
public class RandomNameGenerator
{
private const string c_AdjectivesPath = "Resources/Words/Adjectives.txt";
private string[]? m_Adjectives;
private const string c_NounsPath = "Resources/Words/Nouns.txt";
private string[]? m_Nouns;
private Dictionary<int, string> m_GeneratedNames = new Dictionary<int, string>();
private string? PluginDirectory
{
get { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); }
}
public RandomNameGenerator()
{
try
{
m_Adjectives = File.ReadAllLines($"{PluginDirectory}/{c_AdjectivesPath}");
m_Nouns = File.ReadAllLines($"{PluginDirectory}/{c_NounsPath}");
}
catch (Exception ex)
{
PluginLog.Error(ex, $"RandomNameGenerator failed to create");
}
}
/// <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 string? GetGeneratedName(string str)
{
if (m_Adjectives == null || m_Nouns == null)
{
return null;
}
int hash = GetDeterministicHashCode(str);
if (!m_GeneratedNames.ContainsKey(hash))
{
// Use the seed as the hash so that player always gets the same name
Random random = new Random(hash);
var adjective = m_Adjectives[random.Next(0, m_Adjectives.Length)];
var noun = m_Nouns[random.Next(0, m_Nouns.Length)];
var generatedName = $"{adjective} {noun}";
m_GeneratedNames[hash] = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(generatedName);
}
return m_GeneratedNames[hash];
}
/// <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);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

828
PlayerTags/Resources/Strings.Designer.cs generated Normal file
View File

@@ -0,0 +1,828 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PlayerTags.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PlayerTags.Resources.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Color.
/// </summary>
public static string Loc_CustomColorConfig_IsEnabled {
get {
return ResourceManager.GetString("Loc_CustomColorConfig_IsEnabled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enable a custom color..
/// </summary>
public static string Loc_CustomColorConfig_IsEnabled_Description {
get {
return ResourceManager.GetString("Loc_CustomColorConfig_IsEnabled_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Apply to game object names.
/// </summary>
public static string Loc_CustomTagConfig_FormattedGameObjectNames {
get {
return ResourceManager.GetString("Loc_CustomTagConfig_FormattedGameObjectNames", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A list of game object names to apply the tag to, separated by commas. E.g. &quot;Cloud Strife, Tifa Lockhart&quot;.
/// </summary>
public static string Loc_CustomTagConfig_FormattedGameObjectNames_Description {
get {
return ResourceManager.GetString("Loc_CustomTagConfig_FormattedGameObjectNames_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name.
/// </summary>
public static string Loc_CustomTagConfig_Name {
get {
return ResourceManager.GetString("Loc_CustomTagConfig_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The name of the tag to display..
/// </summary>
public static string Loc_CustomTagConfig_Name_Description {
get {
return ResourceManager.GetString("Loc_CustomTagConfig_Name_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Free company visibility.
/// </summary>
public static string Loc_FreeCompanyNameplateVisibility {
get {
return ResourceManager.GetString("Loc_FreeCompanyNameplateVisibility", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Default.
/// </summary>
public static string Loc_FreeCompanyNameplateVisibility_Default {
get {
return ResourceManager.GetString("Loc_FreeCompanyNameplateVisibility_Default", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The free company on nameplates will only be visible when the character is in a free company..
/// </summary>
public static string Loc_FreeCompanyNameplateVisibility_Default_Description {
get {
return ResourceManager.GetString("Loc_FreeCompanyNameplateVisibility_Default_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The visibility of the free company on nameplates..
/// </summary>
public static string Loc_FreeCompanyNameplateVisibility_Description {
get {
return ResourceManager.GetString("Loc_FreeCompanyNameplateVisibility_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Never visible.
/// </summary>
public static string Loc_FreeCompanyNameplateVisibility_Never {
get {
return ResourceManager.GetString("Loc_FreeCompanyNameplateVisibility_Never", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The free company on nameplates will never be visible..
/// </summary>
public static string Loc_FreeCompanyNameplateVisibility_Never_Description {
get {
return ResourceManager.GetString("Loc_FreeCompanyNameplateVisibility_Never_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Randomly generate player names.
/// </summary>
public static string Loc_IsPlayerNameRandomlyGenerated {
get {
return ResourceManager.GetString("Loc_IsPlayerNameRandomlyGenerated", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use tags to replace every player&apos;s name with a randomly generated one. Helpful for preserving anonymity when taking screenshots..
/// </summary>
public static string Loc_IsPlayerNameRandomlyGenerated_Description {
get {
return ResourceManager.GetString("Loc_IsPlayerNameRandomlyGenerated_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Data element.
/// </summary>
public static string Loc_NameplateElement {
get {
return ResourceManager.GetString("Loc_NameplateElement", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A data element on nameplates..
/// </summary>
public static string Loc_NameplateElement_Description {
get {
return ResourceManager.GetString("Loc_NameplateElement_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Free company.
/// </summary>
public static string Loc_NameplateElement_FreeCompany {
get {
return ResourceManager.GetString("Loc_NameplateElement_FreeCompany", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The free company on nameplates..
/// </summary>
public static string Loc_NameplateElement_FreeCompany_Description {
get {
return ResourceManager.GetString("Loc_NameplateElement_FreeCompany_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name.
/// </summary>
public static string Loc_NameplateElement_Name {
get {
return ResourceManager.GetString("Loc_NameplateElement_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The name on nameplates..
/// </summary>
public static string Loc_NameplateElement_Name_Description {
get {
return ResourceManager.GetString("Loc_NameplateElement_Name_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Title.
/// </summary>
public static string Loc_NameplateElement_Title {
get {
return ResourceManager.GetString("Loc_NameplateElement_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates..
/// </summary>
public static string Loc_NameplateElement_Title_Description {
get {
return ResourceManager.GetString("Loc_NameplateElement_Title_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Role.
/// </summary>
public static string Loc_Role {
get {
return ResourceManager.GetString("Loc_Role", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A character role..
/// </summary>
public static string Loc_Role_Description {
get {
return ResourceManager.GetString("Loc_Role_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DPS.
/// </summary>
public static string Loc_Role_DPS {
get {
return ResourceManager.GetString("Loc_Role_DPS", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The DPS role..
/// </summary>
public static string Loc_Role_DPS_Description {
get {
return ResourceManager.GetString("Loc_Role_DPS_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Healer.
/// </summary>
public static string Loc_Role_Healer {
get {
return ResourceManager.GetString("Loc_Role_Healer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The healer role..
/// </summary>
public static string Loc_Role_Healer_Description {
get {
return ResourceManager.GetString("Loc_Role_Healer_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Land/Hand.
/// </summary>
public static string Loc_Role_LandHand {
get {
return ResourceManager.GetString("Loc_Role_LandHand", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The land/hand role..
/// </summary>
public static string Loc_Role_LandHand_Description {
get {
return ResourceManager.GetString("Loc_Role_LandHand_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tank.
/// </summary>
public static string Loc_Role_Tank {
get {
return ResourceManager.GetString("Loc_Role_Tank", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tank role..
/// </summary>
public static string Loc_Role_Tank_Description {
get {
return ResourceManager.GetString("Loc_Role_Tank_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enabled.
/// </summary>
public static string Loc_RoleOverrideConfig_IsEnabled {
get {
return ResourceManager.GetString("Loc_RoleOverrideConfig_IsEnabled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enable the tag for this role..
/// </summary>
public static string Loc_RoleOverrideConfig_IsEnabled_Description {
get {
return ResourceManager.GetString("Loc_RoleOverrideConfig_IsEnabled_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name.
/// </summary>
public static string Loc_RoleOverrideConfig_Name {
get {
return ResourceManager.GetString("Loc_RoleOverrideConfig_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The name to use for this role when the format is set to use role names..
/// </summary>
public static string Loc_RoleOverrideConfig_Name_Description {
get {
return ResourceManager.GetString("Loc_RoleOverrideConfig_Name_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tag format.
/// </summary>
public static string Loc_RoleTagFormat {
get {
return ResourceManager.GetString("Loc_RoleTagFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Abbreviated job name.
/// </summary>
public static string Loc_RoleTagFormat_AbbreviatedJobName {
get {
return ResourceManager.GetString("Loc_RoleTagFormat_AbbreviatedJobName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tag will appear as an abbreviated job name, e.g. SAM, WHM, GNB..
/// </summary>
public static string Loc_RoleTagFormat_AbbreviatedJobName_Description {
get {
return ResourceManager.GetString("Loc_RoleTagFormat_AbbreviatedJobName_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The format that the role tag should be displayed as..
/// </summary>
public static string Loc_RoleTagFormat_Description {
get {
return ResourceManager.GetString("Loc_RoleTagFormat_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Job name.
/// </summary>
public static string Loc_RoleTagFormat_JobName {
get {
return ResourceManager.GetString("Loc_RoleTagFormat_JobName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tag will appear as a job name in English, e.g. Samurai, White Mage, Gunbreaker..
/// </summary>
public static string Loc_RoleTagFormat_JobName_Description {
get {
return ResourceManager.GetString("Loc_RoleTagFormat_JobName_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Role name.
/// </summary>
public static string Loc_RoleTagFormat_RoleName {
get {
return ResourceManager.GetString("Loc_RoleTagFormat_RoleName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tag will appear as the configured role name, e.g. DPS, Healer, Tank..
/// </summary>
public static string Loc_RoleTagFormat_RoleName_Description {
get {
return ResourceManager.GetString("Loc_RoleTagFormat_RoleName_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add custom tag.
/// </summary>
public static string Loc_Static_AddCustomTag {
get {
return ResourceManager.GetString("Loc_Static_AddCustomTag", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Chat tag.
/// </summary>
public static string Loc_Static_ChatTag {
get {
return ResourceManager.GetString("Loc_Static_ChatTag", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Custom tags.
/// </summary>
public static string Loc_Static_CustomTags {
get {
return ResourceManager.GetString("Loc_Static_CustomTags", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Development.
/// </summary>
public static string Loc_Static_Development {
get {
return ResourceManager.GetString("Loc_Static_Development", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to General.
/// </summary>
public static string Loc_Static_General {
get {
return ResourceManager.GetString("Loc_Static_General", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Jobs.
/// </summary>
public static string Loc_Static_Jobs {
get {
return ResourceManager.GetString("Loc_Static_Jobs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Nameplates.
/// </summary>
public static string Loc_Static_Nameplates {
get {
return ResourceManager.GetString("Loc_Static_Nameplates", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Nameplate tag.
/// </summary>
public static string Loc_Static_NameplateTag {
get {
return ResourceManager.GetString("Loc_Static_NameplateTag", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No custom tags added..
/// </summary>
public static string Loc_Static_NoCustomTagsAdded {
get {
return ResourceManager.GetString("Loc_Static_NoCustomTagsAdded", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Player Tags.
/// </summary>
public static string Loc_Static_PluginName {
get {
return ResourceManager.GetString("Loc_Static_PluginName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove custom tag.
/// </summary>
public static string Loc_Static_RemoveCustomTag {
get {
return ResourceManager.GetString("Loc_Static_RemoveCustomTag", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Role and job tags.
/// </summary>
public static string Loc_Static_RoleAndJobTags {
get {
return ResourceManager.GetString("Loc_Static_RoleAndJobTags", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Roles.
/// </summary>
public static string Loc_Static_Roles {
get {
return ResourceManager.GetString("Loc_Static_Roles", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to String position.
/// </summary>
public static string Loc_StringPosition {
get {
return ResourceManager.GetString("Loc_StringPosition", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to After.
/// </summary>
public static string Loc_StringPosition_After {
get {
return ResourceManager.GetString("Loc_StringPosition_After", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tag will be positioned after the data element..
/// </summary>
public static string Loc_StringPosition_After_Description {
get {
return ResourceManager.GetString("Loc_StringPosition_After_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Before.
/// </summary>
public static string Loc_StringPosition_Before {
get {
return ResourceManager.GetString("Loc_StringPosition_Before", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tag will be positioned before the data element..
/// </summary>
public static string Loc_StringPosition_Before_Description {
get {
return ResourceManager.GetString("Loc_StringPosition_Before_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The position in a string where tags will be displayed..
/// </summary>
public static string Loc_StringPosition_Description {
get {
return ResourceManager.GetString("Loc_StringPosition_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to None.
/// </summary>
public static string Loc_StringPosition_None {
get {
return ResourceManager.GetString("Loc_StringPosition_None", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tag will not be positioned in the string..
/// </summary>
public static string Loc_StringPosition_None_Description {
get {
return ResourceManager.GetString("Loc_StringPosition_None_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Replace.
/// </summary>
public static string Loc_StringPosition_Replace {
get {
return ResourceManager.GetString("Loc_StringPosition_Replace", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The tag will replace its data element..
/// </summary>
public static string Loc_StringPosition_Replace_Description {
get {
return ResourceManager.GetString("Loc_StringPosition_Replace_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Title position.
/// </summary>
public static string Loc_TitleNameplatePosition {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Always above the name.
/// </summary>
public static string Loc_TitleNameplatePosition_AlwaysAboveName {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition_AlwaysAboveName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates will always be positioned above the name..
/// </summary>
public static string Loc_TitleNameplatePosition_AlwaysAboveName_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition_AlwaysAboveName_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Always below the name.
/// </summary>
public static string Loc_TitleNameplatePosition_AlwaysBelowName {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition_AlwaysBelowName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates will always be positioned below the name..
/// </summary>
public static string Loc_TitleNameplatePosition_AlwaysBelowName_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition_AlwaysBelowName_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Default.
/// </summary>
public static string Loc_TitleNameplatePosition_Default {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition_Default", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates will be positioned depending on the title..
/// </summary>
public static string Loc_TitleNameplatePosition_Default_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition_Default_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The position of the title on nameplates..
/// </summary>
public static string Loc_TitleNameplatePosition_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplatePosition_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Title visibility.
/// </summary>
public static string Loc_TitleNameplateVisibility {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Always.
/// </summary>
public static string Loc_TitleNameplateVisibility_Always {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_Always", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates will always be visible, even when the character does not have a title..
/// </summary>
public static string Loc_TitleNameplateVisibility_Always_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_Always_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Default.
/// </summary>
public static string Loc_TitleNameplateVisibility_Default {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_Default", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates will only be visible when the character has a title..
/// </summary>
public static string Loc_TitleNameplateVisibility_Default_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_Default_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The visibility of the title on nameplates..
/// </summary>
public static string Loc_TitleNameplateVisibility_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Never.
/// </summary>
public static string Loc_TitleNameplateVisibility_Never {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_Never", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates will never visible..
/// </summary>
public static string Loc_TitleNameplateVisibility_Never_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_Never_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to When it has tags.
/// </summary>
public static string Loc_TitleNameplateVisibility_WhenHasTags {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_WhenHasTags", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The title on nameplates will only be visible when it has tags..
/// </summary>
public static string Loc_TitleNameplateVisibility_WhenHasTags_Description {
get {
return ResourceManager.GetString("Loc_TitleNameplateVisibility_WhenHasTags_Description", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,375 @@
<?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 id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<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>Player Tags</value>
</data>
<data name="Loc_Static_General" xml:space="preserve">
<value>General</value>
</data>
<data name="Loc_Static_Development" xml:space="preserve">
<value>Development</value>
</data>
<data name="Loc_Static_CustomTags" xml:space="preserve">
<value>Custom tags</value>
</data>
<data name="Loc_Static_AddCustomTag" xml:space="preserve">
<value>Add custom tag</value>
</data>
<data name="Loc_Static_RemoveCustomTag" xml:space="preserve">
<value>Remove custom tag</value>
</data>
<data name="Loc_Static_NoCustomTagsAdded" xml:space="preserve">
<value>No custom tags added.</value>
</data>
<data name="Loc_Static_RoleAndJobTags" xml:space="preserve">
<value>Role and job tags</value>
</data>
<data name="Loc_Static_Roles" xml:space="preserve">
<value>Roles</value>
</data>
<data name="Loc_Static_Jobs" xml:space="preserve">
<value>Jobs</value>
</data>
<data name="Loc_Static_ChatTag" xml:space="preserve">
<value>Chat tag</value>
</data>
<data name="Loc_Static_NameplateTag" xml:space="preserve">
<value>Nameplate tag</value>
</data>
<data name="Loc_Static_Nameplates" xml:space="preserve">
<value>Nameplates</value>
</data>
<data name="Loc_IsPlayerNameRandomlyGenerated" xml:space="preserve">
<value>Randomly generate player names</value>
</data>
<data name="Loc_IsPlayerNameRandomlyGenerated_Description" xml:space="preserve">
<value>Use tags to replace every player's name with a randomly generated one. Helpful for preserving anonymity when taking screenshots.</value>
</data>
<data name="Loc_RoleOverrideConfig_IsEnabled" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Loc_RoleOverrideConfig_IsEnabled_Description" xml:space="preserve">
<value>Enable the tag for this role.</value>
</data>
<data name="Loc_RoleOverrideConfig_Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Loc_RoleOverrideConfig_Name_Description" xml:space="preserve">
<value>The name to use for this role when the format is set to use role names.</value>
</data>
<data name="Loc_CustomColorConfig_IsEnabled" xml:space="preserve">
<value>Color</value>
</data>
<data name="Loc_CustomColorConfig_IsEnabled_Description" xml:space="preserve">
<value>Enable a custom color.</value>
</data>
<data name="Loc_CustomTagConfig_Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Loc_CustomTagConfig_Name_Description" xml:space="preserve">
<value>The name of the tag to display.</value>
</data>
<data name="Loc_CustomTagConfig_FormattedGameObjectNames" xml:space="preserve">
<value>Apply to game object names</value>
</data>
<data name="Loc_CustomTagConfig_FormattedGameObjectNames_Description" xml:space="preserve">
<value>A list of game object names to apply the tag to, separated by commas. E.g. "Cloud Strife, Tifa Lockhart"</value>
</data>
<data name="Loc_FreeCompanyNameplateVisibility" xml:space="preserve">
<value>Free company visibility</value>
</data>
<data name="Loc_FreeCompanyNameplateVisibility_Description" xml:space="preserve">
<value>The visibility of the free company on nameplates.</value>
</data>
<data name="Loc_FreeCompanyNameplateVisibility_Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="Loc_FreeCompanyNameplateVisibility_Default_Description" xml:space="preserve">
<value>The free company on nameplates will only be visible when the character is in a free company.</value>
</data>
<data name="Loc_FreeCompanyNameplateVisibility_Never" xml:space="preserve">
<value>Never visible</value>
</data>
<data name="Loc_FreeCompanyNameplateVisibility_Never_Description" xml:space="preserve">
<value>The free company on nameplates will never be visible.</value>
</data>
<data name="Loc_NameplateElement" xml:space="preserve">
<value>Data element</value>
</data>
<data name="Loc_NameplateElement_Description" xml:space="preserve">
<value>A data element on nameplates.</value>
</data>
<data name="Loc_NameplateElement_FreeCompany" xml:space="preserve">
<value>Free company</value>
</data>
<data name="Loc_NameplateElement_FreeCompany_Description" xml:space="preserve">
<value>The free company on nameplates.</value>
</data>
<data name="Loc_NameplateElement_Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Loc_NameplateElement_Name_Description" xml:space="preserve">
<value>The name on nameplates.</value>
</data>
<data name="Loc_NameplateElement_Title" xml:space="preserve">
<value>Title</value>
</data>
<data name="Loc_NameplateElement_Title_Description" xml:space="preserve">
<value>The title on nameplates.</value>
</data>
<data name="Loc_RoleTagFormat" xml:space="preserve">
<value>Tag format</value>
</data>
<data name="Loc_RoleTagFormat_Description" xml:space="preserve">
<value>The format that the role tag should be displayed as.</value>
</data>
<data name="Loc_RoleTagFormat_AbbreviatedJobName" xml:space="preserve">
<value>Abbreviated job name</value>
</data>
<data name="Loc_RoleTagFormat_JobName" xml:space="preserve">
<value>Job name</value>
</data>
<data name="Loc_RoleTagFormat_AbbreviatedJobName_Description" xml:space="preserve">
<value>The tag will appear as an abbreviated job name, e.g. SAM, WHM, GNB.</value>
</data>
<data name="Loc_RoleTagFormat_JobName_Description" xml:space="preserve">
<value>The tag will appear as a job name in English, e.g. Samurai, White Mage, Gunbreaker.</value>
</data>
<data name="Loc_RoleTagFormat_RoleName" xml:space="preserve">
<value>Role name</value>
</data>
<data name="Loc_RoleTagFormat_RoleName_Description" xml:space="preserve">
<value>The tag will appear as the configured role name, e.g. DPS, Healer, Tank.</value>
</data>
<data name="Loc_Role" xml:space="preserve">
<value>Role</value>
</data>
<data name="Loc_Role_Description" xml:space="preserve">
<value>A character role.</value>
</data>
<data name="Loc_Role_DPS" xml:space="preserve">
<value>DPS</value>
</data>
<data name="Loc_Role_DPS_Description" xml:space="preserve">
<value>The DPS role.</value>
</data>
<data name="Loc_Role_Healer" xml:space="preserve">
<value>Healer</value>
</data>
<data name="Loc_Role_Healer_Description" xml:space="preserve">
<value>The healer role.</value>
</data>
<data name="Loc_Role_LandHand" xml:space="preserve">
<value>Land/Hand</value>
</data>
<data name="Loc_Role_LandHand_Description" xml:space="preserve">
<value>The land/hand role.</value>
</data>
<data name="Loc_Role_Tank" xml:space="preserve">
<value>Tank</value>
</data>
<data name="Loc_Role_Tank_Description" xml:space="preserve">
<value>The tank role.</value>
</data>
<data name="Loc_StringPosition" xml:space="preserve">
<value>String position</value>
</data>
<data name="Loc_StringPosition_Description" xml:space="preserve">
<value>The position in a string where tags will be displayed.</value>
</data>
<data name="Loc_StringPosition_After" xml:space="preserve">
<value>After</value>
</data>
<data name="Loc_StringPosition_After_Description" xml:space="preserve">
<value>The tag will be positioned after the data element.</value>
</data>
<data name="Loc_StringPosition_Before" xml:space="preserve">
<value>Before</value>
</data>
<data name="Loc_StringPosition_Before_Description" xml:space="preserve">
<value>The tag will be positioned before the data element.</value>
</data>
<data name="Loc_StringPosition_None" xml:space="preserve">
<value>None</value>
</data>
<data name="Loc_StringPosition_None_Description" xml:space="preserve">
<value>The tag will not be positioned in the string.</value>
</data>
<data name="Loc_StringPosition_Replace" xml:space="preserve">
<value>Replace</value>
</data>
<data name="Loc_StringPosition_Replace_Description" xml:space="preserve">
<value>The tag will replace its data element.</value>
</data>
<data name="Loc_TitleNameplatePosition" xml:space="preserve">
<value>Title position</value>
</data>
<data name="Loc_TitleNameplatePosition_Description" xml:space="preserve">
<value>The position of the title on nameplates.</value>
</data>
<data name="Loc_TitleNameplatePosition_AlwaysAboveName" xml:space="preserve">
<value>Always above the name</value>
</data>
<data name="Loc_TitleNameplatePosition_AlwaysAboveName_Description" xml:space="preserve">
<value>The title on nameplates will always be positioned above the name.</value>
</data>
<data name="Loc_TitleNameplatePosition_AlwaysBelowName" xml:space="preserve">
<value>Always below the name</value>
</data>
<data name="Loc_TitleNameplatePosition_AlwaysBelowName_Description" xml:space="preserve">
<value>The title on nameplates will always be positioned below the name.</value>
</data>
<data name="Loc_TitleNameplatePosition_Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="Loc_TitleNameplatePosition_Default_Description" xml:space="preserve">
<value>The title on nameplates will be positioned depending on the title.</value>
</data>
<data name="Loc_TitleNameplateVisibility" xml:space="preserve">
<value>Title visibility</value>
</data>
<data name="Loc_TitleNameplateVisibility_Description" xml:space="preserve">
<value>The visibility of the title on nameplates.</value>
</data>
<data name="Loc_TitleNameplateVisibility_Always" xml:space="preserve">
<value>Always</value>
</data>
<data name="Loc_TitleNameplateVisibility_Always_Description" xml:space="preserve">
<value>The title on nameplates will always be visible, even when the character does not have a title.</value>
</data>
<data name="Loc_TitleNameplateVisibility_Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="Loc_TitleNameplateVisibility_Default_Description" xml:space="preserve">
<value>The title on nameplates will only be visible when the character has a title.</value>
</data>
<data name="Loc_TitleNameplateVisibility_Never" xml:space="preserve">
<value>Never</value>
</data>
<data name="Loc_TitleNameplateVisibility_Never_Description" xml:space="preserve">
<value>The title on nameplates will never visible.</value>
</data>
<data name="Loc_TitleNameplateVisibility_WhenHasTags" xml:space="preserve">
<value>When it has tags</value>
</data>
<data name="Loc_TitleNameplateVisibility_WhenHasTags_Description" xml:space="preserve">
<value>The title on nameplates will only be visible when it has tags.</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
namespace PlayerTags
{
public delegate void SetNameplateDelegate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, ref bool isTitleVisible, ref bool isTitleAboveName, ref int iconId, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged);
}

View File

@@ -0,0 +1,97 @@
using Dalamud.Data;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace PlayerTags
{
public static class UIColorHelper
{
private class UIColorComparer : IEqualityComparer<UIColor>
{
public bool Equals(UIColor? left, UIColor? right)
{
if (left != null && right != null)
{
return left.UIForeground == right.UIForeground;
}
return false;
}
public int GetHashCode(UIColor obj)
{
return obj.UIForeground.GetHashCode();
}
}
private static UIColor[] s_UIColors = new UIColor[] { };
public static IEnumerable<UIColor> UIColors { get { return s_UIColors; } }
public static void Initialize(DataManager dataManager)
{
var uiColors = dataManager.GetExcelSheet<UIColor>();
if (uiColors != null)
{
var filteredUIColors = new List<UIColor>(uiColors.Distinct(new UIColorComparer()).Where(uiColor => uiColor.UIForeground != 0 && uiColor.UIForeground != 255));
filteredUIColors.Sort((left, right) =>
{
var leftColor = ToColor(left);
var rightColor = ToColor(right);
ImGui.ColorConvertRGBtoHSV(leftColor.X, leftColor.Y, leftColor.Z, out float leftHue, out float leftSaturation, out float leftValue);
ImGui.ColorConvertRGBtoHSV(rightColor.X, rightColor.Y, rightColor.Z, out float rightHue, out float rightSaturation, out float rightValue);
var hueDifference = leftHue.CompareTo(rightHue);
if (hueDifference != 0)
{
return hueDifference;
}
var valueDifference = leftValue.CompareTo(rightValue);
if (valueDifference != 0)
{
return valueDifference;
}
var saturationDifference = leftSaturation.CompareTo(rightSaturation);
if (saturationDifference != 0)
{
return saturationDifference;
}
return 0;
});
s_UIColors = filteredUIColors.ToArray();
}
}
public static Vector4 ToColor(UIColor uiColor)
{
var uiColorBytes = BitConverter.GetBytes(uiColor.UIForeground);
return
new Vector4((float)uiColorBytes[3] / 255,
(float)uiColorBytes[2] / 255,
(float)uiColorBytes[1] / 255,
(float)uiColorBytes[0] / 255);
}
public static Vector4 ToColor(ushort colorId)
{
foreach (var uiColor in UIColors)
{
if ((ushort)uiColor.RowId == colorId)
{
return ToColor(uiColor);
}
}
return new Vector4();
}
}
}