Second pass with new config ui

This commit is contained in:
r00telement
2021-10-16 03:18:53 +01:00
parent 3f064ba47a
commit 26580089ab
43 changed files with 2193 additions and 1458 deletions

View File

@@ -3,7 +3,7 @@ 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}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlayerTags", "PlayerTags\PlayerTags.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

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

View File

@@ -1,26 +0,0 @@
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

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

View File

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

View File

@@ -1,73 +0,0 @@
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

@@ -1,403 +0,0 @@
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

@@ -1,14 +0,0 @@
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

@@ -1,19 +0,0 @@
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

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

View File

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

View File

@@ -1,14 +0,0 @@
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,97 @@
using Dalamud.Data;
using Dalamud.Game.Text.SeStringHandling;
using Lumina.Excel.GeneratedSheets;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags
{
public class DefaultPluginData
{
public 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 Dictionary<string, Role> RolesByJobAbbreviation { get; } = new Dictionary<string, Role>();
public Dictionary<string, InheritableData> AllTagsChanges = new Dictionary<string, InheritableData>();
public Dictionary<string, InheritableData> AllRoleTagsChanges = new Dictionary<string, InheritableData>();
public Dictionary<Role, Dictionary<string, InheritableData>> RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
public Dictionary<string, Dictionary<string, InheritableData>> JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
public Dictionary<string, InheritableData> AllCustomTagsChanges = new Dictionary<string, InheritableData>();
public void Initialize(DataManager dataManager)
{
AllTagsChanges = new Tag(new LiteralPluginString(""))
{
TagPositionInChat = TagPosition.Before,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
}.GetChanges();
AllRoleTagsChanges = new Tag(new LiteralPluginString(""))
{
IsIconVisibleInChat = true,
IsTextVisibleInNameplates = true,
}.GetChanges();
RoleTagsChanges[Role.LandHand] = new Tag(new LiteralPluginString(""))
{
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
}.GetChanges();
RoleTagsChanges[Role.Tank] = new Tag(new LiteralPluginString(""))
{
Icon = BitmapFontIcon.Tank,
TextColor = 546,
}.GetChanges();
RoleTagsChanges[Role.Healer] = new Tag(new LiteralPluginString(""))
{
Icon = BitmapFontIcon.Healer,
TextColor = 43,
}.GetChanges();
RoleTagsChanges[Role.DPS] = new Tag(new LiteralPluginString(""))
{
Icon = BitmapFontIcon.DPS,
TextColor = 508,
}.GetChanges();
foreach ((var role, var roleTagChanges) in RoleTagsChanges)
{
var classJobs = dataManager.GetExcelSheet<ClassJob>();
if (classJobs == null)
{
break;
}
foreach (var classJob in classJobs.Where(classJob => RolesById[classJob.Role] == role && !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
RolesByJobAbbreviation[classJob.Abbreviation.RawString] = role;
if (!JobTagsChanges.ContainsKey(classJob.Abbreviation.RawString))
{
JobTagsChanges[classJob.Abbreviation.RawString] = new Tag(new LiteralPluginString(""))
{
Text = classJob.Abbreviation.RawString,
}.GetChanges();
}
}
}
AllCustomTagsChanges = new Tag(new LiteralPluginString(""))
{
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
}.GetChanges();
}
}
}

View File

@@ -0,0 +1,30 @@
using Newtonsoft.Json;
using System;
namespace PlayerTags
{
public class GeneralConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null && value.GetType().IsEnum)
{
writer.WriteValue(Enum.GetName(value.GetType(), value));
}
else
{
writer.WriteValue(value);
}
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
return reader.Value;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
namespace PlayerTags
{
public enum InheritableBehavior
{
Inherit,
Enabled,
Disabled
}
}

View File

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

View File

@@ -0,0 +1,60 @@
namespace PlayerTags
{
public class InheritableReference<T> : IInheritable
where T : class
{
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
public T Value;
public T? InheritedValue
{
get
{
IInheritable? current = this;
while (current != null)
{
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 default;
}
}
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
};
}
}
}

View File

@@ -0,0 +1,90 @@
using Dalamud.Logging;
using System;
namespace PlayerTags
{
public class InheritableValue<T> : IInheritable
where T : struct
{
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
public T Value;
public T? InheritedValue
{
get
{
IInheritable? current = this;
while (current != null)
{
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 default;
}
}
public static implicit operator InheritableValue<T>(T value) => new InheritableValue<T>(value)
{
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 (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
{
Value = (T)Convert.ChangeType(inheritableData.Value, typeof(T));
}
}
catch (Exception ex)
{
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

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

View File

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

50
PlayerTags/Localizer.cs Normal file
View File

@@ -0,0 +1,50 @@
using Dalamud.Logging;
using PlayerTags.Resources;
using System;
namespace PlayerTags
{
public static class Localizer
{
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)
{
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;
}
}
}

View File

@@ -1,4 +1,4 @@
namespace PlayerTags.Config
namespace PlayerTags
{
public enum NameplateElement
{

View File

@@ -0,0 +1,8 @@
namespace PlayerTags
{
public enum NameplateFreeCompanyVisibility
{
Default,
Never
}
}

View File

@@ -1,6 +1,6 @@
namespace PlayerTags.Config
namespace PlayerTags
{
public enum TitleNameplatePosition
public enum NameplateTitlePosition
{
Default,
AlwaysAboveName,

View File

@@ -1,6 +1,6 @@
namespace PlayerTags.Config
namespace PlayerTags
{
public enum TitleNameplateVisibility
public enum NameplateTitleVisibility
{
Default,
Always,

View File

@@ -3,9 +3,10 @@
"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/raw/main/PlayerTags/Resources/Promo/Chat_1.png",
"IconUrl": "https://github.com/r00telement/PlayerTags/raw/main/PlayerTags/Resources/Promo/Icon.png",
"ImageUrls": [
"https://github.com/r00telement/PlayerTags/raw/main/PlayerTags/Resources/Promo/Nameplates_1.png",
"https://github.com/r00telement/PlayerTags/raw/main/PlayerTags/Resources/Promo/Chat_1.png",
"https://github.com/r00telement/PlayerTags/raw/main/PlayerTags/Resources/Promo/Nameplates_1.png"
"https://github.com/r00telement/PlayerTags/raw/main/PlayerTags/Resources/Promo/Chat_2.png"
]
}

View File

@@ -1,5 +1,6 @@
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Command;
@@ -10,7 +11,6 @@ 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;
@@ -44,80 +44,114 @@ namespace PlayerTags
[PluginService]
private static CommandManager CommandManager { get; set; } = null!;
private MainConfig m_Config;
[PluginService]
private static ClientState ClientState { get; set; } = null!;
private MainConfigUI m_ConfigUI;
private PluginConfiguration m_PluginConfiguration;
private Dictionary<string, Payload[]> m_JobTagPayloads = new Dictionary<string, Payload[]>();
private PluginConfigurationUI m_PluginConfigurationUI;
private Dictionary<CustomTagConfig, Payload[]> m_CustomTagPayloads = new Dictionary<CustomTagConfig, Payload[]>();
private RandomNameGenerator m_RandomNameGenerator = new RandomNameGenerator();
private PluginHooks? m_PluginHooks = null;
private Dictionary<Tag, Dictionary<TagTarget, Payload[]>> m_TagTargetPayloads = new Dictionary<Tag, Dictionary<TagTarget, Payload[]>>();
private TextPayload m_SpaceTextPayload = new TextPayload($" ");
private PluginHooks m_PluginHooks;
private RandomNameGenerator? m_RandomNameGenerator = null;
private PluginData m_PluginData = new PluginData();
public Plugin()
{
UIColorHelper.Initialize(DataManager);
m_PluginConfiguration = PluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
m_PluginConfiguration.Initialize(PluginInterface);
m_PluginData.Initialize(DataManager, m_PluginConfiguration);
m_PluginConfigurationUI = new PluginConfigurationUI(m_PluginConfiguration, m_PluginData);
m_Config = PluginInterface.GetPluginConfig() as MainConfig ?? new MainConfig();
m_Config.Initialize(PluginInterface, DataManager);
m_Config.Saved += Configuration_Saved;
m_ConfigUI = new MainConfigUI(m_Config);
ClientState.Login += ClientState_Login;
ClientState.Logout += ClientState_Logout;
ClientState.TerritoryChanged += ClientState_TerritoryChanged;
ChatGui.ChatMessage += Chat_ChatMessage;
PluginInterface.UiBuilder.Draw += UiBuilder_Draw;
PluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
m_PluginConfiguration.Saved += PluginConfiguration_Saved;
CommandManager.AddHandler(c_CommandName, new CommandInfo((string command, string arguments) =>
{
m_ConfigUI.IsVisible = true;
m_PluginConfiguration.IsVisible = true;
m_PluginConfiguration.Save(m_PluginData);
})
{
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();
}
Hook();
}
public void Dispose()
{
PluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
Unhook();
CommandManager.RemoveHandler(c_CommandName);
m_PluginConfiguration.Saved -= PluginConfiguration_Saved;
PluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
PluginInterface.UiBuilder.Draw -= UiBuilder_Draw;
ChatGui.ChatMessage -= Chat_ChatMessage;
m_Config.Saved -= Configuration_Saved;
m_PluginHooks?.Dispose();
ClientState.TerritoryChanged -= ClientState_TerritoryChanged;
ClientState.Logout -= ClientState_Logout;
ClientState.Login -= ClientState_Login;
}
private void Configuration_Saved()
private void Hook()
{
if (m_PluginHooks == null)
{
m_PluginHooks = new PluginHooks(Framework, ObjectTable, GameGui, SetNameplate);
}
}
private void Unhook()
{
if (m_PluginHooks != null)
{
m_PluginHooks.Dispose();
m_PluginHooks = null;
}
}
private void Rehook()
{
Unhook();
Hook();
}
private void ClientState_Login(object? sender, EventArgs e)
{
Hook();
}
private void ClientState_Logout(object? sender, EventArgs e)
{
Unhook();
}
private void ClientState_TerritoryChanged(object? sender, ushort e)
{
Rehook();
}
private void PluginConfiguration_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();
}
m_TagTargetPayloads.Clear();
}
private void UiBuilder_Draw()
{
m_ConfigUI.Draw();
m_PluginConfigurationUI.Draw();
}
private void UiBuilder_OpenConfigUi()
{
m_ConfigUI.IsVisible = true;
m_PluginConfiguration.IsVisible = true;
}
private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
@@ -143,105 +177,101 @@ namespace PlayerTags
{
AddTagsToNameplate(gameObject, name, title, freeCompany, out isNameChanged, out isTitleChanged, out isFreeCompanyChanged);
if (m_Config.TitlePosition == TitleNameplatePosition.AlwaysAboveName)
if (m_PluginConfiguration.NameplateTitlePosition == NameplateTitlePosition.AlwaysAboveName)
{
isTitleAboveName = true;
}
else if (m_Config.TitlePosition == TitleNameplatePosition.AlwaysBelowName)
else if (m_PluginConfiguration.NameplateTitlePosition == NameplateTitlePosition.AlwaysBelowName)
{
isTitleAboveName = false;
}
if (m_Config.TitleVisibility == TitleNameplateVisibility.Default)
if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Default)
{
}
else if (m_Config.TitleVisibility == TitleNameplateVisibility.Always)
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Always)
{
isTitleVisible = true;
}
else if (m_Config.TitleVisibility == TitleNameplateVisibility.Never)
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Never)
{
isTitleVisible = false;
}
else if (m_Config.TitleVisibility == TitleNameplateVisibility.WhenHasTags)
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.WhenHasTags)
{
isTitleVisible = isTitleChanged;
}
if (m_Config.FreeCompanyVisibility == FreeCompanyNameplateVisibility.Default)
if (m_PluginConfiguration.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.Default)
{
}
else if (m_Config.FreeCompanyVisibility == FreeCompanyNameplateVisibility.Never)
else if (m_PluginConfiguration.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.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)
private Payload[] CreateTagPayloads(TagTarget tagTarget, Tag tag)
{
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)
BitmapFontIcon? icon = null;
if (tagTarget == TagTarget.Chat && tag.IsIconVisibleInChat.InheritedValue != null && tag.IsIconVisibleInChat.InheritedValue.Value)
{
colorId = roleConfig.JobOverrideConfigs[jobAbbreviation].CustomColor.Id!.Value;
icon = tag.Icon.InheritedValue;
}
else if (roleConfig.CustomColor.Id != null)
else if (tagTarget == TagTarget.Nameplate && tag.IsIconVisibleInNameplates.InheritedValue != null && tag.IsIconVisibleInNameplates.InheritedValue.Value)
{
colorId = roleConfig.CustomColor.Id.Value;
icon = tag.Icon.InheritedValue;
}
// If we picked a color id, add the payloads for it
if (colorId != null)
string? text = null;
if (tagTarget == TagTarget.Chat && tag.IsTextVisibleInChat.InheritedValue != null && tag.IsTextVisibleInChat.InheritedValue.Value)
{
newPayloads.Insert(0, new UIForegroundPayload(colorId.Value));
newPayloads.Add(new UIForegroundPayload(0));
text = tag.Text.InheritedValue;
}
else if (tagTarget == TagTarget.Nameplate && tag.IsTextVisibleInNameplates.InheritedValue != null && tag.IsTextVisibleInNameplates.InheritedValue.Value)
{
text = tag.Text.InheritedValue;
}
var newPayloadsArray = newPayloads.ToArray();
m_JobTagPayloads[jobAbbreviation] = newPayloadsArray;
if (!m_TagTargetPayloads.ContainsKey(tag))
{
m_TagTargetPayloads[tag] = new Dictionary<TagTarget, Payload[]>();
}
return newPayloadsArray;
if (icon != null && icon.Value != BitmapFontIcon.None)
{
newPayloads.Add(new IconPayload(icon.Value));
}
if (!string.IsNullOrWhiteSpace(text))
{
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
{
newPayloads.Add(new EmphasisItalicPayload(true));
}
if (tag.TextColor.InheritedValue != null)
{
newPayloads.Add(new UIForegroundPayload(tag.TextColor.InheritedValue.Value));
}
newPayloads.Add(new TextPayload(text));
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
{
newPayloads.Add(new EmphasisItalicPayload(false));
}
if (tag.TextColor.InheritedValue != null)
{
newPayloads.Add(new UIForegroundPayload(0));
}
}
return newPayloads.ToArray();
}
/// <summary>
@@ -249,51 +279,83 @@ namespace PlayerTags
/// </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)
private IEnumerable<Payload> GetTagPayloads(TagTarget tagTarget, Tag tag)
{
if (m_CustomTagPayloads.TryGetValue(customTagConfig, out var payloads))
if (m_TagTargetPayloads.TryGetValue(tag, out var tagTargetPayloads))
{
return payloads;
if (tagTargetPayloads.TryGetValue(tagTarget, out var payloads))
{
return payloads;
}
}
else
{
m_TagTargetPayloads[tag] = new Dictionary<TagTarget, Payload[]>();
}
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;
m_TagTargetPayloads[tag][tagTarget] = CreateTagPayloads(tagTarget, tag);
return m_TagTargetPayloads[tag][tagTarget];
}
/// <summary>
/// Adds an additional space text payload in between any existing text payloads.
/// Adds an additional space text payload in between any existing text payloads. If there is an icon payload between two text payloads then the space is skipped.
/// Also adds an extra space to the beginning or end depending on the tag position and whether the most significant payload in either direction is a text payload.
/// In spirit, this is to ensure there is always a space between 2 text payloads, including between these payloads and the target payload.
/// </summary>
/// <param name="payloads">The payloads to add spaces between.</param>
private void AddSpacesBetweenTextPayloads(List<Payload> payloads)
private void AddSpacesBetweenTextPayloads(List<Payload> payloads, TagPosition tagPosition)
{
var textPayloads = payloads.Where(payload => payload is TextPayload).ToList();
foreach (var textPayload in textPayloads.Skip(1))
if (payloads == null)
{
var index = payloads.IndexOf(textPayload);
payloads.Insert(index, m_SpaceTextPayload);
return;
}
if (!payloads.Any())
{
return;
}
List<int> indicesToInsertSpacesAt = new List<int>();
int lastTextPayloadIndex = -1;
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, m_SpaceTextPayload);
}
// Decide whether to add a space to the end
if (tagPosition == TagPosition.Before)
{
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
if (significantPayloads.Last() is TextPayload)
{
payloads.Add(m_SpaceTextPayload);
}
}
// Decide whether to add a space to the beginning
else if (tagPosition == TagPosition.After)
{
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
if (significantPayloads.First() is TextPayload)
{
payloads.Insert(0, m_SpaceTextPayload);
}
}
}
@@ -313,15 +375,19 @@ namespace PlayerTags
public TextPayload TextPayload { get; init; }
/// <summary>
/// The matching game object.
/// The matching game object if one exists
/// </summary>
public GameObject? GameObject { get; init; }
public StringMatch(SeString seString, TextPayload textPayload, GameObject? gameObject = null)
/// <summary>
/// A matching player payload if one exists.
/// </summary>
public PlayerPayload? PlayerPayload { get; init; }
public StringMatch(SeString seString, TextPayload textPayload)
{
SeString = seString;
TextPayload = textPayload;
GameObject = gameObject;
}
/// <summary>
@@ -358,7 +424,11 @@ namespace PlayerTags
// 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);
var stringMatch = new StringMatch(seString, textPayload)
{
GameObject = gameObject,
PlayerPayload = playerPayload
};
stringMatches.Add(stringMatch);
// Don't handle the text payload twice
@@ -370,18 +440,25 @@ namespace PlayerTags
}
}
/// TODO: Not sure if this is desirable. Enabling this allows tags to appear next to the name of the local player by text in chat because the local player doesn't have a player payload.
/// But because it's just a simple string comparison, it won't work in all circumstances. E.g. in party chat the player name is wrapped in (). To be comprehensive we need to search substring.
/// This means we would need to think about breaking down existing payloads to split them out.
/// If we decide to do that, we could even for example find unlinked player names in chat and add player payloads for them.
// 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));
//else if (payload is TextPayload textPayload)
//{
// var gameObject = ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == textPayload.Text);
// var isIncludedInCustomTagConfig = m_Config.CustomTags.Any(customTagConfig => customTagConfig.IncludesGameObjectName(textPayload.Text));
if (gameObject != null || isIncludedInCustomTagConfig)
{
var stringMatch = new StringMatch(seString, textPayload, gameObject);
stringMatches.Add(stringMatch);
}
}
// if (gameObject != null || isIncludedInCustomTagConfig)
// {
// var stringMatch = new StringMatch(seString, textPayload)
// {
// GameObject = gameObject
// };
// stringMatches.Add(stringMatch);
// }
//}
}
return stringMatches;
@@ -390,32 +467,37 @@ namespace PlayerTags
/// <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="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>
private void AddPayloadChanges(StringPosition stringPosition, IEnumerable<Payload> payloads, Dictionary<StringPosition, List<Payload>> stringChanges)
private void AddPayloadChanges(TagPosition tagPosition, IEnumerable<Payload> payloads, Dictionary<TagPosition, List<Payload>> stringChanges)
{
if (!payloads.Any())
if (payloads == null || !payloads.Any())
{
return;
}
if (!stringChanges.Keys.Contains(stringPosition))
if (stringChanges == null)
{
stringChanges[stringPosition] = new List<Payload>();
return;
}
stringChanges[stringPosition].AddRange(payloads);
if (!stringChanges.Keys.Contains(tagPosition))
{
stringChanges[tagPosition] = new List<Payload>();
}
stringChanges[tagPosition].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="tagPosition">The position 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)
private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable<Payload> payloads, Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>> nameplateChanges)
{
if (!payloads.Any())
{
@@ -424,10 +506,10 @@ namespace PlayerTags
if (!nameplateChanges.Keys.Contains(nameplateElement))
{
nameplateChanges[nameplateElement] = new Dictionary<StringPosition, List<Payload>>();
nameplateChanges[nameplateElement] = new Dictionary<TagPosition, List<Payload>>();
}
AddPayloadChanges(stringPosition, payloads, nameplateChanges[nameplateElement]);
AddPayloadChanges(tagPosition, payloads, nameplateChanges[nameplateElement]);
}
/// <summary>
@@ -436,53 +518,71 @@ namespace PlayerTags
/// <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)
private void ApplyStringChanges(SeString seString, Dictionary<TagPosition, List<Payload>> stringChanges, Payload? anchorPayload = null)
{
foreach ((var stringPosition, var payloads) in stringChanges)
if (stringChanges.Count == 0)
{
if (!payloads.Any())
{
continue;
}
return;
}
AddSpacesBetweenTextPayloads(payloads);
List<TagPosition> tagPositionsOrdered = new List<TagPosition>();
// If there's no anchor payload, do replaces first so that befores and afters are based on the replaced data
if (anchorPayload == null)
{
tagPositionsOrdered.Add(TagPosition.Replace);
}
if (stringPosition == StringPosition.Before)
tagPositionsOrdered.Add(TagPosition.Before);
tagPositionsOrdered.Add(TagPosition.After);
// If there is an anchor payload, do replaces last so that we still know which payload needs to be removed
if (anchorPayload != null)
{
tagPositionsOrdered.Add(TagPosition.Replace);
}
foreach (var tagPosition in tagPositionsOrdered)
{
if (stringChanges.TryGetValue(tagPosition, out var payloads) && payloads.Any())
{
if (anchorPayload != null)
AddSpacesBetweenTextPayloads(stringChanges[tagPosition], tagPosition);
if (tagPosition == TagPosition.Before)
{
var payloadIndex = seString.Payloads.IndexOf(anchorPayload);
seString.Payloads.InsertRange(payloadIndex, payloads.Append(m_SpaceTextPayload));
if (anchorPayload != null)
{
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
seString.Payloads.InsertRange(anchorPayloadIndex, payloads);
}
else
{
seString.Payloads.InsertRange(0, payloads);
}
}
else
else if (tagPosition == TagPosition.After)
{
seString.Payloads.InsertRange(0, payloads.Append(m_SpaceTextPayload));
if (anchorPayload != null)
{
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
seString.Payloads.InsertRange(anchorPayloadIndex + 1, payloads);
}
else
{
seString.Payloads.AddRange(payloads);
}
}
}
else if (stringPosition == StringPosition.After)
{
if (anchorPayload != null)
else if (tagPosition == TagPosition.Replace)
{
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);
if (anchorPayload != null)
{
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
seString.Payloads.InsertRange(anchorPayloadIndex, payloads);
seString.Payloads.Remove(anchorPayload);
}
else
{
seString.Payloads.Clear();
seString.Payloads.AddRange(payloads);
}
}
}
}
@@ -504,18 +604,25 @@ namespace PlayerTags
isTitleChanged = false;
isFreeCompanyChanged = false;
Dictionary<NameplateElement, Dictionary<StringPosition, List<Payload>>> nameplateChanges = new Dictionary<NameplateElement, Dictionary<StringPosition, List<Payload>>>();
Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>> nameplateChanges = new Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>>();
if (gameObject is Character character)
{
// Add the role tag payloads
if (m_Config.RoleTag.NameplatePosition != StringPosition.None)
// Add the job tag
if (m_PluginData.JobTags.TryGetValue(character.ClassJob.GameData.Abbreviation, out var jobTag))
{
AddPayloadChanges(m_Config.RoleTag.NameplateElement, m_Config.RoleTag.NameplatePosition, GetJobTagPayloads(character), nameplateChanges);
if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null)
{
var payloads = GetTagPayloads(TagTarget.Nameplate, jobTag);
if (payloads.Any())
{
AddPayloadChanges(jobTag.TagTargetInNameplates.InheritedValue.Value, jobTag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges);
}
}
}
// Add randomly generated name tag payload
if (m_Config.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator != null)
// Add the randomly generated name tag payload
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator != null)
{
var characterName = character.Name.TextValue;
if (characterName != null)
@@ -523,18 +630,25 @@ namespace PlayerTags
var generatedName = m_RandomNameGenerator.GetGeneratedName(characterName);
if (generatedName != null)
{
AddPayloadChanges(NameplateElement.Name, StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges);
AddPayloadChanges(NameplateElement.Name, TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges);
}
}
}
}
// Add the custom tag payloads
foreach (var customTagConfig in m_Config.CustomTagConfigs)
foreach (var customTag in m_PluginData.CustomTags)
{
if (customTagConfig.NameplatePosition != StringPosition.None && customTagConfig.FormattedGameObjectNames.Split(',').Contains(gameObject.Name.TextValue))
if (customTag.TagTargetInNameplates.InheritedValue != null && customTag.TagPositionInNameplates.InheritedValue != null)
{
AddPayloadChanges(customTagConfig.NameplateElement, customTagConfig.NameplatePosition, GetCustomTagPayloads(customTagConfig), nameplateChanges);
if (customTag.IncludesGameObjectNameToApplyTo(gameObject.Name.TextValue))
{
var payloads = GetTagPayloads(TagTarget.Nameplate, customTag);
if (payloads.Any())
{
AddPayloadChanges(customTag.TagTargetInNameplates.InheritedValue.Value, customTag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges);
}
}
}
}
@@ -577,37 +691,52 @@ namespace PlayerTags
var stringMatches = GetStringMatches(message);
foreach (var stringMatch in stringMatches)
{
Dictionary<StringPosition, List<Payload>> stringChanges = new Dictionary<StringPosition, List<Payload>>();
Dictionary<TagPosition, List<Payload>> stringChanges = new Dictionary<TagPosition, List<Payload>>();
// The role tag payloads
if (stringMatch.GameObject is Character character)
{
if (m_Config.RoleTag.ChatPosition != StringPosition.None)
// Add the job tag
if (m_PluginData.JobTags.TryGetValue(character.ClassJob.GameData.Abbreviation, out var jobTag))
{
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)
if (jobTag.TagPositionInChat.InheritedValue != null)
{
AddPayloadChanges(StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges);
var payloads = GetTagPayloads(TagTarget.Chat, jobTag);
if (payloads.Any())
{
AddPayloadChanges(jobTag.TagPositionInChat.InheritedValue.Value, payloads, stringChanges);
}
}
}
// Add randomly generated name tag payload
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator != null)
{
var playerName = stringMatch.GetMatchText();
if (playerName != null)
{
var generatedName = m_RandomNameGenerator.GetGeneratedName(playerName);
if (generatedName != null)
{
AddPayloadChanges(TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges);
}
}
}
}
// Add the custom tag payloads
foreach (var customTagConfig in m_Config.CustomTagConfigs)
foreach (var customTag in m_PluginData.CustomTags)
{
if (customTagConfig.IncludesGameObjectName(stringMatch.GetMatchText()))
if (customTag.TagPositionInChat.InheritedValue != null)
{
AddPayloadChanges(customTagConfig.ChatPosition, GetCustomTagPayloads(customTagConfig), stringChanges);
if (customTag.IncludesGameObjectNameToApplyTo(stringMatch.GetMatchText()))
{
var customTagPayloads = GetTagPayloads(TagTarget.Chat, customTag);
if (customTagPayloads.Any())
{
AddPayloadChanges(customTag.TagPositionInChat.InheritedValue.Value, customTagPayloads, stringChanges);
}
}
}
}

View File

@@ -0,0 +1,94 @@
using Dalamud.Configuration;
using Dalamud.Plugin;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags
{
[Serializable]
public class PluginConfiguration : IPluginConfiguration
{
public int Version { get; set; } = 0;
public bool IsVisible = false;
public NameplateFreeCompanyVisibility NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility.Default;
public NameplateTitleVisibility NameplateTitleVisibility = NameplateTitleVisibility.WhenHasTags;
public NameplateTitlePosition NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName;
public bool IsPlayerNameRandomlyGenerated = false;
[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<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>>();
[NonSerialized]
private DalamudPluginInterface? m_PluginInterface;
public event System.Action? Saved;
public void Initialize(DalamudPluginInterface pluginInterface)
{
m_PluginInterface = pluginInterface;
}
public void Save(PluginData pluginData)
{
AllTagsChanges = pluginData.AllTags.GetChanges(pluginData.Default.AllTagsChanges);
AllRoleTagsChanges = pluginData.AllRoleTags.GetChanges(pluginData.Default.AllRoleTagsChanges);
RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
foreach ((var role, var roleTag) in pluginData.RoleTags)
{
Dictionary<string, InheritableData>? defaultChanges;
pluginData.Default.RoleTagsChanges.TryGetValue(role, out defaultChanges);
var changes = roleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RoleTagsChanges[role] = changes;
}
}
JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
foreach ((var jobAbbreviation, var jobTag) in pluginData.JobTags)
{
Dictionary<string, InheritableData>? defaultChanges;
pluginData.Default.JobTagsChanges.TryGetValue(jobAbbreviation, out defaultChanges);
var changes = jobTag.GetChanges(defaultChanges);
if (changes.Any())
{
JobTagsChanges[jobAbbreviation] = changes;
}
}
AllCustomTagsChanges = pluginData.AllCustomTags.GetChanges(pluginData.Default.AllCustomTagsChanges);
CustomTagsChanges = new List<Dictionary<string, InheritableData>>();
foreach (var customTag in pluginData.CustomTags)
{
CustomTagsChanges.Add(customTag.GetChanges());
}
if (m_PluginInterface != null)
{
m_PluginInterface.SavePluginConfig(this);
Saved?.Invoke();
};
}
}
}

View File

@@ -0,0 +1,571 @@
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using PlayerTags.Resources;
using System;
using System.Linq;
using System.Numerics;
namespace PlayerTags
{
public class PluginConfigurationUI
{
private PluginConfiguration m_PluginConfiguration;
private PluginData m_PluginData;
private InheritableValue<ushort>? m_ColorPickerPopupDataContext;
public PluginConfigurationUI(PluginConfiguration config, PluginData pluginData)
{
m_PluginConfiguration = config;
m_PluginData = pluginData;
}
public void Draw()
{
if (m_PluginConfiguration == null || !m_PluginConfiguration.IsVisible)
{
return;
}
if (ImGui.Begin(Strings.Loc_Static_PluginName, ref m_PluginConfiguration.IsVisible))
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1, 0.8f, 0.5f, 1));
ImGui.TextWrapped(Strings.Loc_Static_WarningMessage);
ImGui.PopStyleColor();
DrawHeading(Strings.Loc_Static_Nameplates);
ImGui.TreePush();
DrawComboBox(true, true, false, ref m_PluginConfiguration.NameplateFreeCompanyVisibility, () => m_PluginConfiguration.Save(m_PluginData));
DrawComboBox(true, true, false, ref m_PluginConfiguration.NameplateTitleVisibility, () => m_PluginConfiguration.Save(m_PluginData));
DrawComboBox(true, true, false, ref m_PluginConfiguration.NameplateTitlePosition, () => m_PluginConfiguration.Save(m_PluginData));
ImGui.TreePop();
DrawHeading(Strings.Loc_Static_Development);
ImGui.TreePush();
DrawCheckbox(nameof(m_PluginConfiguration.IsPlayerNameRandomlyGenerated), true, ref m_PluginConfiguration.IsPlayerNameRandomlyGenerated, () => m_PluginConfiguration.Save(m_PluginData));
ImGui.TreePop();
ImGui.EndTabItem();
DrawHeading(Strings.Loc_Static_Tags);
ImGui.TreePush();
int depth = 0;
Draw(m_PluginData.AllTags, ref depth);
if (ImGui.Button(Strings.Loc_Static_AddCustomTag))
{
var newTag = new Tag(new LocalizedPluginString(nameof(PluginData.CustomTags)))
{
Text = "",
GameObjectNamesToApplyTo = ""
};
m_PluginData.CustomTags.Add(newTag);
newTag.Parent = m_PluginData.AllCustomTags;
}
ImGui.TreePop();
ImGui.End();
}
if (!m_PluginConfiguration.IsVisible)
{
m_PluginConfiguration.Save(m_PluginData);
}
}
public void Draw(Tag tag, ref int depth)
{
ImGui.PushID(tag.GetHashCode().ToString());
ImGui.BeginGroup();
ImGui.BeginChild(tag.GetHashCode().ToString(), new Vector2(100, 60));
var text = tag.Name.Value;
var textSize = ImGui.CalcTextSize(text);
var addButtonSize = new Vector2(23, 23);
var panelSize = ImGui.GetItemRectSize();
ImGui.SetCursorPosX(panelSize.X - textSize.X);
ImGui.Text(text);
if (ImGui.IsPopupOpen("AddPopup"))
{
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0.2f, 0.6f, 0.2f, 1));
}
else
{
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0.1f, 0.3f, 0.1f, 1));
}
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.2f, 0.6f, 0.2f, 1));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.2f, 0.6f, 0.2f, 1));
ImGui.SetCursorPosX(panelSize.X - addButtonSize.X);
var addPopupPos = ImGui.GetCursorPos();
addPopupPos.Y -= addButtonSize.Y + 2;
if (ImGui.Button("+", addButtonSize))
{
ImGui.OpenPopup("AddPopup");
}
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.PopStyleColor();
bool wasPaddingConsumed = false;
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 5));
ImGui.SetNextWindowPos(ImGui.GetCursorScreenPos() + addPopupPos);
if (ImGui.BeginPopup("AddPopup"))
{
wasPaddingConsumed = true;
ImGui.PopStyleVar();
ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0f, 0f, 0f, 0));
if (ImGui.BeginListBox("###SelectableInheritables"))
{
var selectableInheritables = tag.Inheritables.Where(inheritable => inheritable.Value.Behavior == InheritableBehavior.Inherit).ToDictionary(item => item.Key, item => item.Value);
var selectableInheritablesWithLocalizedNames = selectableInheritables
.Select(inheritable => new { Name = inheritable.Key, LocalizedName = Localizer.GetString(inheritable.Key, false) })
.OrderBy(item => item.LocalizedName);
foreach (var inheritableLocalizedName in selectableInheritablesWithLocalizedNames)
{
bool isSelected = false;
if (ImGui.Selectable(inheritableLocalizedName.LocalizedName, isSelected))
{
selectableInheritables[inheritableLocalizedName.Name].Behavior = InheritableBehavior.Enabled;
if (selectableInheritables[inheritableLocalizedName.Name] is InheritableValue<bool> inheritableBool)
{
inheritableBool.Value = true;
}
m_PluginConfiguration.Save(m_PluginData);
ImGui.CloseCurrentPopup();
}
if (isSelected)
{
ImGui.SetItemDefaultFocus();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Localizer.GetString(inheritableLocalizedName.Name, true));
}
}
ImGui.EndListBox();
}
ImGui.PopStyleColor();
ImGui.EndPopup();
}
if (!wasPaddingConsumed)
{
ImGui.PopStyleVar();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Strings.Loc_Static_AddPropertyOverride_Description);
}
ImGui.EndChild();
ImGui.EndGroup();
ImGui.SameLine();
ImGui.BeginGroup();
var selectedInheritables = tag.Inheritables.Where(inheritable => inheritable.Value.Behavior != InheritableBehavior.Inherit).ToDictionary(item => item.Key, item => item.Value);
var selectedInheritablesWithLocalizedNames = selectedInheritables
.Select(inheritable => new { Name = inheritable.Key, LocalizedName = Localizer.GetString(inheritable.Key, false) })
.OrderBy(item => item.LocalizedName);
foreach (var selectedInheritablesWithLocalizedName in selectedInheritablesWithLocalizedNames)
{
switch (selectedInheritablesWithLocalizedName.Name)
{
case nameof(tag.Icon):
ImGui.SameLine();
DrawInheritable(nameof(tag.Icon), false, true, tag.Icon);
break;
case nameof(tag.IsIconVisibleInChat):
ImGui.SameLine();
DrawInheritable(nameof(tag.IsIconVisibleInChat), tag.IsIconVisibleInChat);
break;
case nameof(tag.IsIconVisibleInNameplates):
ImGui.SameLine();
DrawInheritable(nameof(tag.IsIconVisibleInNameplates), tag.IsIconVisibleInNameplates);
break;
case nameof(tag.Text):
ImGui.SameLine();
DrawInheritable(nameof(tag.Text), tag.Text);
break;
case nameof(tag.TextColor):
ImGui.SameLine();
DrawInheritable(nameof(tag.TextColor), tag.TextColor);
break;
case nameof(tag.IsTextItalic):
ImGui.SameLine();
DrawInheritable(nameof(tag.IsTextItalic), tag.IsTextItalic);
break;
case nameof(tag.IsTextVisibleInChat):
ImGui.SameLine();
DrawInheritable(nameof(tag.IsTextVisibleInChat), tag.IsTextVisibleInChat);
break;
case nameof(tag.IsTextVisibleInNameplates):
ImGui.SameLine();
DrawInheritable(nameof(tag.IsTextVisibleInNameplates), tag.IsTextVisibleInNameplates);
break;
case nameof(tag.TagPositionInChat):
ImGui.SameLine();
DrawInheritable(nameof(tag.TagPositionInChat), true, false, tag.TagPositionInChat);
break;
case nameof(tag.TagPositionInNameplates):
ImGui.SameLine();
DrawInheritable(nameof(tag.TagPositionInNameplates), true, false, tag.TagPositionInNameplates);
break;
case nameof(tag.TagTargetInNameplates):
ImGui.SameLine();
DrawInheritable(nameof(tag.TagTargetInNameplates), true, false, tag.TagTargetInNameplates);
break;
case nameof(tag.GameObjectNamesToApplyTo):
ImGui.SameLine();
DrawInheritable(nameof(tag.GameObjectNamesToApplyTo), tag.GameObjectNamesToApplyTo);
break;
default:
break;
}
}
if (m_PluginData.CustomTags.Contains(tag))
{
ImGui.SameLine();
ImGui.BeginGroup();
ImGui.Indent();
if (ImGui.Button(Strings.Loc_Static_RemoveCustomTag))
{
m_PluginData.AllCustomTags.Children.Remove(tag);
m_PluginData.CustomTags.Remove(tag);
m_PluginConfiguration.Save(m_PluginData);
}
ImGui.EndGroup();
}
ImGui.EndGroup();
foreach (var childTag in tag.Children.ToArray())
{
depth++;
Draw(childTag, ref depth);
}
depth++;
ImGui.PopID();
}
private void DrawRemoveButton(IInheritable inheritable)
{
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(0, 0));
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0.3f, 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($"x", new Vector2(23, 23)))
{
inheritable.Behavior = InheritableBehavior.Inherit;
m_PluginConfiguration.Save(m_PluginData);
}
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.PopStyleVar();
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Strings.Loc_Static_RemovePropertyOverride_Description);
}
}
private void DrawInheritable(string localizedStringName, InheritableValue<bool> inheritable)
{
ImGui.BeginGroup();
ImGui.Indent();
ImGui.BeginChild(inheritable.GetHashCode().ToString(), new Vector2(180, 47));
ImGui.Text(Localizer.GetString(localizedStringName, false));
DrawCheckbox("IsEnabled", false, ref inheritable.Value, () =>
{
m_PluginConfiguration.Save(m_PluginData);
});
ImGui.SameLine();
DrawRemoveButton(inheritable);
ImGui.EndChild();
ImGui.EndGroup();
}
private void DrawInheritable<TEnum>(string localizedStringName, bool shouldLocalizeNames, bool shouldOrderNames, InheritableValue<TEnum> inheritable)
where TEnum : struct, Enum
{
ImGui.BeginGroup();
ImGui.Indent();
ImGui.BeginChild(inheritable.GetHashCode().ToString(), new Vector2(180, 47));
ImGui.Text(Localizer.GetString(localizedStringName, false));
bool isEnabled = inheritable.Behavior == InheritableBehavior.Enabled;
DrawCheckbox("IsEnabled", false, ref isEnabled, () =>
{
if (isEnabled)
{
inheritable.Behavior = InheritableBehavior.Enabled;
}
else
{
inheritable.Behavior = InheritableBehavior.Disabled;
}
m_PluginConfiguration.Save(m_PluginData);
});
if (isEnabled)
{
ImGui.SameLine();
DrawComboBox(false, shouldLocalizeNames, shouldOrderNames, ref inheritable.Value, () => { m_PluginConfiguration.Save(m_PluginData); });
}
ImGui.SameLine();
DrawRemoveButton(inheritable);
ImGui.EndChild();
ImGui.EndGroup();
}
private void DrawInheritable(string localizedStringName, InheritableValue<ushort> inheritable)
{
ImGui.BeginGroup();
ImGui.Indent();
ImGui.BeginChild(inheritable.GetHashCode().ToString(), new Vector2(180, 47));
ImGui.Text(Localizer.GetString(localizedStringName, false));
bool isEnabled = inheritable.Behavior == InheritableBehavior.Enabled;
DrawCheckbox("IsEnabled", false, ref isEnabled, () =>
{
if (isEnabled)
{
inheritable.Behavior = InheritableBehavior.Enabled;
}
else
{
inheritable.Behavior = InheritableBehavior.Disabled;
}
m_PluginConfiguration.Save(m_PluginData);
});
if (isEnabled)
{
ImGui.SameLine();
DrawColorButton(
inheritable.Value.ToString(),
UIColorHelper.ToColor(inheritable.Value),
() =>
{
m_ColorPickerPopupDataContext = inheritable;
ImGui.OpenPopup("ColorPickerPopup");
});
}
bool wasStyleConsumed = false;
ImGui.SetNextWindowPos(ImGui.GetCursorScreenPos() + new Vector2(31, 0));
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
if (ImGui.BeginPopup("ColorPickerPopup"))
{
wasStyleConsumed = true;
ImGui.PopStyleVar();
DrawUIColorPicker(
(UIColor value) =>
{
if (m_ColorPickerPopupDataContext != null)
{
m_ColorPickerPopupDataContext.Value = (ushort)value.RowId;
m_ColorPickerPopupDataContext = null;
m_PluginConfiguration.Save(m_PluginData);
}
ImGui.CloseCurrentPopup();
});
ImGui.EndPopup();
}
if (!wasStyleConsumed)
{
ImGui.PopStyleVar();
}
ImGui.SameLine();
DrawRemoveButton(inheritable);
ImGui.EndChild();
ImGui.EndGroup();
}
private void DrawInheritable(string localizedStringName, InheritableReference<string> inheritable)
{
ImGui.BeginGroup();
ImGui.Indent();
ImGui.BeginChild(inheritable.GetHashCode().ToString(), new Vector2(180, 47));
ImGui.Text(Localizer.GetString(localizedStringName, false));
bool isEnabled = inheritable.Behavior == InheritableBehavior.Enabled;
DrawCheckbox("IsEnabled", false, ref isEnabled, () =>
{
if (isEnabled)
{
inheritable.Behavior = InheritableBehavior.Enabled;
}
else
{
inheritable.Behavior = InheritableBehavior.Disabled;
}
m_PluginConfiguration.Save(m_PluginData);
});
if (isEnabled)
{
ImGui.SameLine();
DrawTextBox(localizedStringName, ref inheritable.Value, () => { m_PluginConfiguration.Save(m_PluginData); });
}
ImGui.SameLine();
DrawRemoveButton(inheritable);
ImGui.EndChild();
ImGui.EndGroup();
}
private void DrawHeading(string label)
{
ImGui.TextColored(new Vector4(0.7f, 0.6f, 1f, 1f), label);
}
private void DrawComboBox<TEnum>(bool isLabelVisible, bool shouldLocalizeNames, bool shouldOrderNames, ref TEnum currentValue, System.Action changed)
where TEnum : Enum
{
if (isLabelVisible)
{
ImGui.Text(Localizer.GetString<TEnum>(false));
}
var currentDisplayName = shouldLocalizeNames ? Localizer.GetString(currentValue, false) : currentValue.ToString();
ImGui.SetNextItemWidth(ImGui.CalcItemWidth());
if (ImGui.BeginCombo($"###{currentValue.GetType().Name}", currentDisplayName))
{
var displayNames = Enum.GetValues(typeof(TEnum)).Cast<TEnum>()
.Select(value => new { Value = value, DisplayName = shouldLocalizeNames ? Localizer.GetString(value, false) : value.ToString() });
if (shouldOrderNames)
{
displayNames = displayNames.OrderBy(displayEnum => displayEnum.DisplayName);
}
foreach (var orderedDisplayName in displayNames)
{
bool isSelected = orderedDisplayName.Value.Equals(currentValue);
if (ImGui.Selectable(orderedDisplayName.DisplayName, isSelected))
{
currentValue = orderedDisplayName.Value;
changed();
}
if (isSelected)
{
ImGui.SetItemDefaultFocus();
}
if (ImGui.IsItemHovered() && shouldLocalizeNames)
{
ImGui.SetTooltip(Localizer.GetString(orderedDisplayName.Value, true));
}
}
ImGui.EndCombo();
}
if (ImGui.IsItemHovered() && shouldLocalizeNames)
{
ImGui.SetTooltip(Localizer.GetString(currentValue, true));
}
}
private void DrawCheckbox(string localizedStringName, bool hasLabel, ref bool isChecked, System.Action changed)
{
if (ImGui.Checkbox(hasLabel ? Localizer.GetString(localizedStringName, false) : $"###{Localizer.GetString(localizedStringName, false)}", ref isChecked))
{
changed();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Localizer.GetString(localizedStringName, true));
}
}
private void DrawColorButton(string colorId, Vector4 color, System.Action clicked)
{
ImGui.PushStyleColor(ImGuiCol.Button, color);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, color);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, color);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0));
ImGui.PushStyleVar(ImGuiStyleVar.ItemInnerSpacing, new Vector2(0, 0));
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
if (ImGui.Button($"###{colorId}", new Vector2(23, 23)))
{
clicked();
}
ImGui.PopStyleVar();
ImGui.PopStyleVar();
ImGui.PopStyleVar();
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.PopStyleColor();
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(colorId);
}
}
private void DrawTextBox(string localizedStringName, ref string text, System.Action changed)
{
ImGui.SetNextItemWidth(ImGui.CalcItemWidth());
var oldText = text;
ImGui.InputText($"###{localizedStringName}", ref text, 1024);
if (text != oldText)
{
changed();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Localizer.GetString(localizedStringName, true));
}
}
private void DrawUIColorPicker(System.Action<UIColor> colorSelected)
{
const int columnCount = 12;
int currentColumn = 0;
foreach (var uiColor in UIColorHelper.UIColors)
{
if (currentColumn % columnCount != 0)
{
ImGui.SameLine(0, 0);
}
DrawColorButton(uiColor.RowId.ToString(), UIColorHelper.ToColor(uiColor), () => { colorSelected(uiColor); });
currentColumn++;
}
}
}
}

99
PlayerTags/PluginData.cs Normal file
View File

@@ -0,0 +1,99 @@
using Dalamud.Data;
using System;
using System.Collections.Generic;
namespace PlayerTags
{
public class PluginData
{
public DefaultPluginData Default;
public Tag AllTags;
public Tag AllRoleTags;
public Dictionary<Role, Tag> RoleTags;
public Dictionary<string, Tag> JobTags;
public Tag AllCustomTags;
public List<Tag> CustomTags;
public PluginData()
{
Default = new DefaultPluginData();
AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)));
AllRoleTags = new Tag(new LocalizedPluginString(nameof(AllRoleTags)));
RoleTags = new Dictionary<Role, Tag>();
JobTags = new Dictionary<string, Tag>();
AllCustomTags = new Tag(new LocalizedPluginString(nameof(AllCustomTags)));
CustomTags = new List<Tag>();
}
public void Initialize(DataManager dataManager, PluginConfiguration pluginConfiguration)
{
Default.Initialize(dataManager);
// Set the default changes and saved changes
AllTags.SetChanges(Default.AllTagsChanges);
AllTags.SetChanges(pluginConfiguration.AllTagsChanges);
AllRoleTags.SetChanges(Default.AllRoleTagsChanges);
AllRoleTags.SetChanges(pluginConfiguration.AllRoleTagsChanges);
foreach (var role in Enum.GetValues<Role>())
{
RoleTags[role] = new Tag(new LocalizedPluginString(Localizer.GetName(role)));
if (Default.RoleTagsChanges.TryGetValue(role, out var defaultChanges))
{
RoleTags[role].SetChanges(defaultChanges);
}
if (pluginConfiguration.RoleTagsChanges.TryGetValue(role, out var savedChanges))
{
RoleTags[role].SetChanges(savedChanges);
}
}
JobTags = new Dictionary<string, Tag>();
foreach ((var jobAbbreviation, var role) in Default.RolesByJobAbbreviation)
{
JobTags[jobAbbreviation] = new Tag(new LiteralPluginString(jobAbbreviation));
if (Default.JobTagsChanges.TryGetValue(jobAbbreviation, out var defaultChanges))
{
JobTags[jobAbbreviation].SetChanges(defaultChanges);
}
if (pluginConfiguration.JobTagsChanges.TryGetValue(jobAbbreviation, out var savedChanges))
{
JobTags[jobAbbreviation].SetChanges(savedChanges);
}
}
AllCustomTags.SetChanges(Default.AllCustomTagsChanges);
AllCustomTags.SetChanges(pluginConfiguration.AllCustomTagsChanges);
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;
}
foreach ((var jobAbbreviation, var jobTag) in JobTags)
{
jobTag.Parent = RoleTags[Default.RolesByJobAbbreviation[jobAbbreviation]];
}
AllCustomTags.Parent = AllTags;
foreach (var tag in CustomTags)
{
tag.Parent = AllCustomTags;
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 KiB

After

Width:  |  Height:  |  Size: 699 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -120,256 +120,241 @@
<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 name="Loc_Static_WarningMessage" xml:space="preserve">
<value>This plugin can modify nameplates. When combined with other plugins that modify nameplates, you are likely to see unexpected behavior.</value>
</data>
<data name="Loc_Static_Nameplates" xml:space="preserve">
<value>Nameplates</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility" xml:space="preserve">
<value>Free company visibility</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_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_NameplateFreeCompanyVisibility_Never" xml:space="preserve">
<value>Never visible</value>
</data>
<data name="Loc_NameplateFreeCompanyVisibility_Never_Description" xml:space="preserve">
<value>The free company on nameplates will never be visible.</value>
</data>
<data name="Loc_NameplateTitleVisibility" xml:space="preserve">
<value>Title visibility</value>
</data>
<data name="Loc_NameplateTitleVisibility_Always" xml:space="preserve">
<value>Always visible</value>
</data>
<data name="Loc_NameplateTitleVisibility_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_NameplateTitleVisibility_Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="Loc_NameplateTitleVisibility_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_NameplateTitleVisibility_Never" xml:space="preserve">
<value>Never visible</value>
</data>
<data name="Loc_NameplateTitleVisibility_Never_Description" xml:space="preserve">
<value>The title on nameplates will never visible.</value>
</data>
<data name="Loc_NameplateTitleVisibility_WhenHasTags" xml:space="preserve">
<value>Only visible when it has tags</value>
</data>
<data name="Loc_NameplateTitleVisibility_WhenHasTags_Description" xml:space="preserve">
<value>The title on nameplates will only be visible when it has tags.</value>
</data>
<data name="Loc_NameplateTitlePosition" xml:space="preserve">
<value>Title position</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysAboveName" xml:space="preserve">
<value>Always above name</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysAboveName_Description" xml:space="preserve">
<value>The title on nameplates will always be positioned above the name.</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysBelowName" xml:space="preserve">
<value>Always below name</value>
</data>
<data name="Loc_NameplateTitlePosition_AlwaysBelowName_Description" xml:space="preserve">
<value>The title on nameplates will always be positioned below the name.</value>
</data>
<data name="Loc_NameplateTitlePosition_Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="Loc_NameplateTitlePosition_Default_Description" xml:space="preserve">
<value>The title on nameplates will be positioned depending on the title.</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 element 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 element 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 element on nameplates.</value>
</data>
<data name="Loc_Static_Development" xml:space="preserve">
<value>Development</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 name="Loc_Static_Tags" xml:space="preserve">
<value>Tags</value>
</data>
<data name="Loc_RoleOverrideConfig_IsEnabled_Description" xml:space="preserve">
<value>Enable the tag for this role.</value>
<data name="Loc_AllTags" xml:space="preserve">
<value>All</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 name="Loc_AllRoleTags" xml:space="preserve">
<value>All Roles</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 name="Loc_Role_Healer" xml:space="preserve">
<value>Healer</value>
</data>
<data name="Loc_StringPosition" xml:space="preserve">
<value>String position</value>
<data name="Loc_Role_DPS" xml:space="preserve">
<value>DPS</value>
</data>
<data name="Loc_StringPosition_Description" xml:space="preserve">
<value>The position in a string where tags will be displayed.</value>
<data name="Loc_AllCustomTags" xml:space="preserve">
<value>All Custom</value>
</data>
<data name="Loc_StringPosition_After" xml:space="preserve">
<data name="Loc_CustomTags" xml:space="preserve">
<value>Custom</value>
</data>
<data name="Loc_IsEnabled_Description" xml:space="preserve">
<value>Enable the value of this override.</value>
</data>
<data name="Loc_Static_AddPropertyOverride_Description" xml:space="preserve">
<value>Add an override.</value>
</data>
<data name="Loc_Static_RemovePropertyOverride_Description" xml:space="preserve">
<value>Remove this override. The value will be inherited.</value>
</data>
<data name="Loc_Static_AddCustomTag" xml:space="preserve">
<value>Add a custom tag</value>
</data>
<data name="Loc_Static_RemoveCustomTag" xml:space="preserve">
<value>Remove this custom tag</value>
</data>
<data name="Loc_Icon" xml:space="preserve">
<value>Icon</value>
</data>
<data name="Loc_Icon_Description" xml:space="preserve">
<value>The icon that will be displayed in the tag.</value>
</data>
<data name="Loc_IsIconVisibleInChat" xml:space="preserve">
<value>Icon visible in chat</value>
</data>
<data name="Loc_IsIconVisibleInChat_Description" xml:space="preserve">
<value>Whether the icon is visible in the tag in chat.</value>
</data>
<data name="Loc_IsIconVisibleInNameplates" xml:space="preserve">
<value>Icon visible in nameplates</value>
</data>
<data name="Loc_IsIconVisibleInNameplates_Description" xml:space="preserve">
<value>Whether the icon is visible in the tag in nameplates.</value>
</data>
<data name="Loc_Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="Loc_Text_Description" xml:space="preserve">
<value>The text that will be displayed in the tag.</value>
</data>
<data name="Loc_TextColor" xml:space="preserve">
<value>Text color</value>
</data>
<data name="Loc_TextColor_Description" xml:space="preserve">
<value>The color of the text in the tag.</value>
</data>
<data name="Loc_IsTextItalic" xml:space="preserve">
<value>Text italic</value>
</data>
<data name="Loc_IsTextItalic_Description" xml:space="preserve">
<value>Whether the text in the tag is italic.</value>
</data>
<data name="Loc_IsTextVisibleInChat" xml:space="preserve">
<value>Text visible in chat</value>
</data>
<data name="Loc_IsTextVisibleInChat_Description" xml:space="preserve">
<value>Whether the text is visible in the tag in chat.</value>
</data>
<data name="Loc_IsTextVisibleInNameplates" xml:space="preserve">
<value>Text visible in nameplates</value>
</data>
<data name="Loc_IsTextVisibleInNameplates_Description" xml:space="preserve">
<value>Whether the text is visible in the tag in nameplates.</value>
</data>
<data name="Loc_TagPositionInChat" xml:space="preserve">
<value>Tag position in chat</value>
</data>
<data name="Loc_TagPositionInChat_Description" xml:space="preserve">
<value>Where the tag should be positioned in chat.</value>
</data>
<data name="Loc_TagPositionInNameplates" xml:space="preserve">
<value>Tag position in nameplates</value>
</data>
<data name="Loc_TagPositionInNameplates_Description" xml:space="preserve">
<value>Where the tag should be positioned in nameplates.</value>
</data>
<data name="Loc_TagTargetInNameplates" xml:space="preserve">
<value>Tag target in nameplates</value>
</data>
<data name="Loc_TagTargetInNameplates_Description" xml:space="preserve">
<value>The element that the tag should target in nameplates.</value>
</data>
<data name="Loc_GameObjectNamesToApplyTo" xml:space="preserve">
<value>Game object names to apply to</value>
</data>
<data name="Loc_GameObjectNamesToApplyTo_Description" xml:space="preserve">
<value>A list of game object names to always apply tags to, separated by commas or semi-colons. E.g. "Cloud Strife, Tifa Lockhart".</value>
</data>
<data name="Loc_TagPosition_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 name="Loc_TagPosition_After_Description" xml:space="preserve">
<value>Display the tag after the target element.</value>
</data>
<data name="Loc_StringPosition_Before" xml:space="preserve">
<data name="Loc_TagPosition_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 name="Loc_TagPosition_Before_Description" xml:space="preserve">
<value>Display the tag before the target 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">
<data name="Loc_TagPosition_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 name="Loc_TagPosition_Replace_Description" xml:space="preserve">
<value>Replace the target element with the tag.</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 name="Loc_IsEnabled" xml:space="preserve">
<value>Enabled</value>
</data>
</root>

View File

@@ -189,6 +189,7 @@ beta
better
bewildered
bewitched
biblical
big
biggish
bigheaded

View File

@@ -1,4 +1,4 @@
namespace PlayerTags.Config
namespace PlayerTags
{
public enum Role
{

140
PlayerTags/Tag.cs Normal file
View File

@@ -0,0 +1,140 @@
using Dalamud.Game.Text.SeStringHandling;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags
{
public class Tag
{
public IPluginString Name { get; init; }
private Tag? m_Parent = null;
public Tag? Parent
{
get => m_Parent;
set
{
if (m_Parent != value)
{
if (m_Parent != null)
{
if (m_Parent.Children.Contains(this))
{
m_Parent.Children.Remove(this);
}
}
m_Parent = value;
if (m_Parent != null)
{
m_Parent.Children.Add(this);
foreach ((var name, IInheritable inheritable) in Inheritables)
{
inheritable.Parent = m_Parent.Inheritables[name];
}
}
}
}
}
public List<Tag> Children { get; } = new List<Tag>();
private Dictionary<string, IInheritable>? m_Inheritables = null;
public Dictionary<string, IInheritable> Inheritables
{
get
{
if (m_Inheritables == null)
{
m_Inheritables = new Dictionary<string, IInheritable>();
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)
{
Inheritables[inheritableField.Name] = inheritable;
}
}
}
return m_Inheritables!;
}
}
public InheritableValue<BitmapFontIcon> Icon = new InheritableValue<BitmapFontIcon>(BitmapFontIcon.Aethernet);
public InheritableValue<bool> IsIconVisibleInChat = new InheritableValue<bool>(false);
public InheritableValue<bool> IsIconVisibleInNameplates = new InheritableValue<bool>(false);
public InheritableReference<string> Text = new InheritableReference<string>("");
public InheritableValue<ushort> TextColor = new InheritableValue<ushort>(6);
public InheritableValue<bool> IsTextItalic = new InheritableValue<bool>(false);
public InheritableValue<bool> IsTextVisibleInChat = new InheritableValue<bool>(false);
public InheritableValue<bool> IsTextVisibleInNameplates = new InheritableValue<bool>(false);
public InheritableValue<TagPosition> TagPositionInChat = new InheritableValue<TagPosition>(TagPosition.Before);
public InheritableValue<TagPosition> TagPositionInNameplates = new InheritableValue<TagPosition>(TagPosition.Before);
public InheritableValue<NameplateElement> TagTargetInNameplates = new InheritableValue<NameplateElement>(NameplateElement.Name);
public InheritableReference<string> GameObjectNamesToApplyTo = new InheritableReference<string>("");
private string[] CleanGameObjectNamesToApplyTo
{
get
{
if (GameObjectNamesToApplyTo == null || GameObjectNamesToApplyTo.InheritedValue == null)
{
return new string[] { };
}
return GameObjectNamesToApplyTo.InheritedValue.Split(';', ',').Select(gameObjectName => gameObjectName.ToLower().Trim()).ToArray();
}
}
public Tag(IPluginString name)
{
Name = name;
}
public bool IncludesGameObjectNameToApplyTo(string gameObjectName)
{
return CleanGameObjectNamesToApplyTo.Contains(gameObjectName.ToLower());
}
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))
{
var inheritableData = inheritable.GetData();
if (inheritableData.Behavior != defaultInheritableData.Behavior ||
!inheritableData.Value.Equals(defaultInheritableData.Value))
{
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)
{
changes[name] = inheritable.GetData();
}
}
return changes;
}
public void SetChanges(Dictionary<string, InheritableData> changes)
{
foreach ((var name, var inheritableData) in changes)
{
Inheritables[name].SetData(inheritableData);
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace PlayerTags
{
public enum TagPosition
{
Before,
After,
Replace
}
}

8
PlayerTags/TagTarget.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace PlayerTags
{
public enum TagTarget
{
Chat,
Nameplate
}
}