Added profile priority system, fixed a few issues
This commit is contained in:
@@ -46,7 +46,7 @@ public partial class CustomizePlusIpc
|
|||||||
.Select(x =>
|
.Select(x =>
|
||||||
{
|
{
|
||||||
string path = _profileFileSystem.FindLeaf(x, out var leaf) ? leaf.FullName() : x.Name.Text;
|
string path = _profileFileSystem.FindLeaf(x, out var leaf) ? leaf.FullName() : x.Name.Text;
|
||||||
return (x.UniqueId, x.Name.Text, path, x.Characters[0].ToNameWithoutOwnerName(), x.Enabled); //todo: proper update to v5
|
return (x.UniqueId, x.Name.Text, path, x.Characters.Count > 0 ? x.Characters[0].ToNameWithoutOwnerName() : "", x.Enabled); //todo: proper update to v5
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,6 +413,7 @@ public unsafe sealed class ArmatureManager : IDisposable
|
|||||||
type is not ProfileChanged.Type.TemporaryProfileDeleted &&
|
type is not ProfileChanged.Type.TemporaryProfileDeleted &&
|
||||||
type is not ProfileChanged.Type.AddedCharacter &&
|
type is not ProfileChanged.Type.AddedCharacter &&
|
||||||
type is not ProfileChanged.Type.RemovedCharacter &&
|
type is not ProfileChanged.Type.RemovedCharacter &&
|
||||||
|
type is not ProfileChanged.Type.PriorityChanged &&
|
||||||
type is not ProfileChanged.Type.ChangedDefaultProfile &&
|
type is not ProfileChanged.Type.ChangedDefaultProfile &&
|
||||||
type is not ProfileChanged.Type.ChangedDefaultLocalPlayerProfile)
|
type is not ProfileChanged.Type.ChangedDefaultLocalPlayerProfile)
|
||||||
return;
|
return;
|
||||||
@@ -438,6 +439,26 @@ public unsafe sealed class ArmatureManager : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(type == ProfileChanged.Type.PriorityChanged)
|
||||||
|
{
|
||||||
|
if (!profile.Enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var character in profile.Characters)
|
||||||
|
{
|
||||||
|
if (!character.IsValid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var armature in GetArmaturesForCharacter(character))
|
||||||
|
{
|
||||||
|
armature.IsPendingProfileRebind = true;
|
||||||
|
_logger.Debug($"ArmatureManager.OnProfileChange profile {profile} priority changed, planning rebind for armature {armature}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (type == ProfileChanged.Type.Toggled)
|
if (type == ProfileChanged.Type.Toggled)
|
||||||
{
|
{
|
||||||
if (!profile.Enabled && profile.Armatures.Count == 0)
|
if (!profile.Enabled && profile.Armatures.Count == 0)
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ internal static class Constants
|
|||||||
internal static class Colors
|
internal static class Colors
|
||||||
{
|
{
|
||||||
public static Vector4 Normal = new Vector4(1, 1, 1, 1);
|
public static Vector4 Normal = new Vector4(1, 1, 1, 1);
|
||||||
|
public static Vector4 Info = new Vector4(0.3f, 0.5f, 1f, 1);
|
||||||
public static Vector4 Warning = new Vector4(1, 0.5f, 0, 1);
|
public static Vector4 Warning = new Vector4(1, 0.5f, 0, 1);
|
||||||
public static Vector4 Error = new Vector4(1, 0, 0, 1);
|
public static Vector4 Error = new Vector4(1, 0, 0, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ public class CommandService : IDisposable
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo: support for multiple profiles
|
||||||
Profile? targetProfile = null;
|
Profile? targetProfile = null;
|
||||||
|
|
||||||
characterName = subArgumentList[0].Trim();
|
characterName = subArgumentList[0].Trim();
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ public sealed class Profile : ISavable
|
|||||||
|
|
||||||
public ProfileType ProfileType { get; set; }
|
public ProfileType ProfileType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Profile priority when there are several profiles affecting same character
|
||||||
|
/// </summary>
|
||||||
|
public int Priority { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tells us if this profile is not persistent (ex. was made via IPC calls) and should have specific treatement like not being shown in UI, etc.
|
/// Tells us if this profile is not persistent (ex. was made via IPC calls) and should have specific treatement like not being shown in UI, etc.
|
||||||
/// WARNING, TEMPLATES FOR TEMPORARY PROFILES *ARE NOT* STORED IN TemplateManager
|
/// WARNING, TEMPLATES FOR TEMPORARY PROFILES *ARE NOT* STORED IN TemplateManager
|
||||||
@@ -107,6 +112,7 @@ public sealed class Profile : ISavable
|
|||||||
["Name"] = Name.Text,
|
["Name"] = Name.Text,
|
||||||
["Enabled"] = Enabled,
|
["Enabled"] = Enabled,
|
||||||
["IsWriteProtected"] = IsWriteProtected,
|
["IsWriteProtected"] = IsWriteProtected,
|
||||||
|
["Priority"] = Priority,
|
||||||
["Templates"] = SerializeTemplates()
|
["Templates"] = SerializeTemplates()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public sealed class ProfileChanged() : EventWrapper<ProfileChanged.Type, Profile
|
|||||||
Deleted,
|
Deleted,
|
||||||
Renamed,
|
Renamed,
|
||||||
Toggled,
|
Toggled,
|
||||||
|
PriorityChanged,
|
||||||
AddedCharacter,
|
AddedCharacter,
|
||||||
RemovedCharacter,
|
RemovedCharacter,
|
||||||
//ChangedCharacter,
|
//ChangedCharacter,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Penumbra.String;
|
|||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Penumbra.GameData.Gui;
|
using Penumbra.GameData.Gui;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
namespace CustomizePlus.Profiles;
|
namespace CustomizePlus.Profiles;
|
||||||
|
|
||||||
@@ -53,20 +54,16 @@ public partial class ProfileManager : IDisposable
|
|||||||
|
|
||||||
foreach (var profile in Profiles)
|
foreach (var profile in Profiles)
|
||||||
{
|
{
|
||||||
//This will solve any issues if file on disk was manually edited and we have more than a single active profile
|
|
||||||
if (profile.Enabled)
|
|
||||||
SetEnabled(profile, true, true);
|
|
||||||
|
|
||||||
if (_configuration.DefaultProfile == profile.UniqueId)
|
if (_configuration.DefaultProfile == profile.UniqueId)
|
||||||
DefaultProfile = profile;
|
DefaultProfile = profile;
|
||||||
|
|
||||||
|
if (_configuration.DefaultLocalPlayerProfile == profile.UniqueId)
|
||||||
|
DefaultLocalPlayerProfile = profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
//insert temp profiles back into profile list
|
//insert temp profiles back into profile list
|
||||||
if (temporaryProfiles.Count > 0)
|
if (temporaryProfiles.Count > 0)
|
||||||
{
|
|
||||||
Profiles.AddRange(temporaryProfiles);
|
Profiles.AddRange(temporaryProfiles);
|
||||||
Profiles.Sort((x, y) => y.IsTemporary.CompareTo(x.IsTemporary));
|
|
||||||
}
|
|
||||||
|
|
||||||
var failed = MoveInvalidNames(invalidNames);
|
var failed = MoveInvalidNames(invalidNames);
|
||||||
if (invalidNames.Count > 0)
|
if (invalidNames.Count > 0)
|
||||||
@@ -135,6 +132,8 @@ public partial class ProfileManager : IDisposable
|
|||||||
{
|
{
|
||||||
var profile = LoadProfileV4V5(obj);
|
var profile = LoadProfileV4V5(obj);
|
||||||
|
|
||||||
|
profile.Priority = obj["Priority"]?.ToObject<int>() ?? throw new ArgumentNullException("Priority");
|
||||||
|
|
||||||
if (obj["Characters"] is not JArray characterArray)
|
if (obj["Characters"] is not JArray characterArray)
|
||||||
return profile;
|
return profile;
|
||||||
|
|
||||||
|
|||||||
@@ -248,51 +248,11 @@ public partial class ProfileManager : IDisposable
|
|||||||
if (profile.Enabled == value && !force)
|
if (profile.Enabled == value && !force)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var oldValue = profile.Enabled;
|
profile.Enabled = value;
|
||||||
|
|
||||||
if (value)
|
SaveProfile(profile);
|
||||||
{
|
|
||||||
_logger.Debug($"Setting {profile} as enabled...");
|
|
||||||
|
|
||||||
foreach (var otherProfile in Profiles)
|
_event.Invoke(ProfileChanged.Type.Toggled, profile, value);
|
||||||
{
|
|
||||||
if (otherProfile == profile || !otherProfile.Enabled || otherProfile.IsTemporary)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool shouldDisable = false;
|
|
||||||
|
|
||||||
//my god this is ugly
|
|
||||||
foreach(var otherCharacter in otherProfile.Characters)
|
|
||||||
{
|
|
||||||
foreach(var currentCharacter in profile.Characters)
|
|
||||||
{
|
|
||||||
if(otherCharacter.MatchesIgnoringOwnership(currentCharacter))
|
|
||||||
{
|
|
||||||
shouldDisable = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldDisable)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(shouldDisable)
|
|
||||||
{
|
|
||||||
_logger.Debug($"\t-> {otherProfile} disabled");
|
|
||||||
SetEnabled(otherProfile, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldValue != value)
|
|
||||||
{
|
|
||||||
profile.Enabled = value;
|
|
||||||
|
|
||||||
SaveProfile(profile);
|
|
||||||
|
|
||||||
_event.Invoke(ProfileChanged.Type.Toggled, profile, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetEnabled(Guid guid, bool value)
|
public void SetEnabled(Guid guid, bool value)
|
||||||
@@ -306,6 +266,21 @@ public partial class ProfileManager : IDisposable
|
|||||||
throw new ProfileNotFoundException();
|
throw new ProfileNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetPriority(Profile profile, int value)
|
||||||
|
{
|
||||||
|
if (profile.Priority == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (value > int.MaxValue || value < int.MinValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
profile.Priority = value;
|
||||||
|
|
||||||
|
SaveProfile(profile);
|
||||||
|
|
||||||
|
_event.Invoke(ProfileChanged.Type.PriorityChanged, profile, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void DeleteTemplate(Profile profile, int templateIndex)
|
public void DeleteTemplate(Profile profile, int templateIndex)
|
||||||
{
|
{
|
||||||
_logger.Debug($"Deleting template #{templateIndex} from {profile}...");
|
_logger.Debug($"Deleting template #{templateIndex} from {profile}...");
|
||||||
@@ -408,6 +383,7 @@ public partial class ProfileManager : IDisposable
|
|||||||
|
|
||||||
profile.Enabled = true;
|
profile.Enabled = true;
|
||||||
profile.ProfileType = ProfileType.Temporary;
|
profile.ProfileType = ProfileType.Temporary;
|
||||||
|
profile.Priority = int.MaxValue; //Make sure temporary profile is always at max priority
|
||||||
|
|
||||||
var permanentIdentifier = identifier.CreatePermanent();
|
var permanentIdentifier = identifier.CreatePermanent();
|
||||||
profile.Characters.Clear();
|
profile.Characters.Clear();
|
||||||
@@ -423,9 +399,6 @@ public partial class ProfileManager : IDisposable
|
|||||||
|
|
||||||
Profiles.Add(profile);
|
Profiles.Add(profile);
|
||||||
|
|
||||||
//Make sure temporary profiles come first, so they are returned by all other methods first
|
|
||||||
Profiles.Sort((x, y) => y.IsTemporary.CompareTo(x.IsTemporary));
|
|
||||||
|
|
||||||
_logger.Debug($"Added temporary profile for {permanentIdentifier}");
|
_logger.Debug($"Added temporary profile for {permanentIdentifier}");
|
||||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileAdded, profile, null);
|
_event.Invoke(ProfileChanged.Type.TemporaryProfileAdded, profile, null);
|
||||||
}
|
}
|
||||||
@@ -457,7 +430,7 @@ public partial class ProfileManager : IDisposable
|
|||||||
if (!actor.Identifier(_actorManager, out var identifier))
|
if (!actor.Identifier(_actorManager, out var identifier))
|
||||||
throw new ActorNotFoundException();
|
throw new ActorNotFoundException();
|
||||||
|
|
||||||
var profile = Profiles.FirstOrDefault(x => x.Characters[0] == identifier && x.IsTemporary);
|
var profile = Profiles.FirstOrDefault(x => x.Characters.Count == 1 && x.Characters[0] == identifier && x.IsTemporary);
|
||||||
if (profile == null)
|
if (profile == null)
|
||||||
throw new ProfileNotFoundException();
|
throw new ProfileNotFoundException();
|
||||||
|
|
||||||
@@ -478,7 +451,7 @@ public partial class ProfileManager : IDisposable
|
|||||||
if (enabledOnly)
|
if (enabledOnly)
|
||||||
query = query.Where(x => x.Enabled);
|
query = query.Where(x => x.Enabled);
|
||||||
|
|
||||||
var profile = query.FirstOrDefault();
|
var profile = query.OrderByDescending(x => x.Priority).FirstOrDefault();
|
||||||
|
|
||||||
if (profile == null)
|
if (profile == null)
|
||||||
return null;
|
return null;
|
||||||
@@ -529,7 +502,7 @@ public partial class ProfileManager : IDisposable
|
|||||||
if (_templateEditorManager.IsEditorActive && _templateEditorManager.EditorProfile.Enabled && IsProfileAppliesToCurrentActor(_templateEditorManager.EditorProfile))
|
if (_templateEditorManager.IsEditorActive && _templateEditorManager.EditorProfile.Enabled && IsProfileAppliesToCurrentActor(_templateEditorManager.EditorProfile))
|
||||||
yield return _templateEditorManager.EditorProfile;
|
yield return _templateEditorManager.EditorProfile;
|
||||||
|
|
||||||
foreach (var profile in Profiles)
|
foreach (var profile in Profiles.OrderByDescending(x => x.Priority))
|
||||||
{
|
{
|
||||||
if(profile.Enabled && IsProfileAppliesToCurrentActor(profile))
|
if(profile.Enabled && IsProfileAppliesToCurrentActor(profile))
|
||||||
yield return profile;
|
yield return profile;
|
||||||
@@ -553,7 +526,7 @@ public partial class ProfileManager : IDisposable
|
|||||||
if (template == null)
|
if (template == null)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
foreach (var profile in Profiles)
|
foreach (var profile in Profiles.OrderByDescending(x => x.Priority))
|
||||||
if (profile.Templates.Contains(template))
|
if (profile.Templates.Contains(template))
|
||||||
yield return profile;
|
yield return profile;
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class StateMonitoringTab
|
|||||||
|
|
||||||
private void DrawProfiles()
|
private void DrawProfiles()
|
||||||
{
|
{
|
||||||
foreach (var profile in _profileManager.Profiles.OrderByDescending(x => x.Enabled))
|
foreach (var profile in _profileManager.Profiles.OrderByDescending(x => x.Enabled).ThenByDescending(x => x.Priority))
|
||||||
{
|
{
|
||||||
DrawSingleProfile("root", profile);
|
DrawSingleProfile("root", profile);
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
@@ -141,7 +141,7 @@ public class StateMonitoringTab
|
|||||||
//characterName = characterName.Incognify();
|
//characterName = characterName.Incognify();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var show = ImGui.CollapsingHeader($"[{(profile.Enabled ? "E" : "D")}] {name} on {characterName} [{profile.ProfileType}] [{profile.UniqueId}]###{prefix}-profile-{profile.UniqueId}");
|
var show = ImGui.CollapsingHeader($"[{(profile.Enabled ? "E" : "D")}] [P:{profile.Priority}] {name} on {characterName} [{profile.ProfileType}] [{profile.UniqueId}]###{prefix}-profile-{profile.UniqueId}");
|
||||||
|
|
||||||
if (!show)
|
if (!show)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ public class ProfileFileSystemSelector : FileSystemSelector<Profile, ProfileStat
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo: priority check
|
||||||
var identifier = _gameObjectService.GetCurrentPlayerActorIdentifier();
|
var identifier = _gameObjectService.GetCurrentPlayerActorIdentifier();
|
||||||
if (leaf.Value.Enabled)
|
if (leaf.Value.Enabled)
|
||||||
state.Color = leaf.Value.Characters.Any(x => x.MatchesIgnoringOwnership(identifier)) ? ColorId.LocalCharacterEnabledProfile : ColorId.EnabledProfile;
|
state.Color = leaf.Value.Characters.Any(x => x.MatchesIgnoringOwnership(identifier)) ? ColorId.LocalCharacterEnabledProfile : ColorId.EnabledProfile;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using Penumbra.String;
|
|||||||
using static FFXIVClientStructs.FFXIV.Client.LayoutEngine.ILayoutInstance;
|
using static FFXIVClientStructs.FFXIV.Client.LayoutEngine.ILayoutInstance;
|
||||||
using CustomizePlus.GameData.Extensions;
|
using CustomizePlus.GameData.Extensions;
|
||||||
using CustomizePlus.Core.Extensions;
|
using CustomizePlus.Core.Extensions;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
|
|
||||||
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
|
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ public class ProfilePanel
|
|||||||
private readonly TemplateEditorEvent _templateEditorEvent;
|
private readonly TemplateEditorEvent _templateEditorEvent;
|
||||||
|
|
||||||
private string? _newName;
|
private string? _newName;
|
||||||
|
private int? _newPriority;
|
||||||
private Profile? _changedProfile;
|
private Profile? _changedProfile;
|
||||||
|
|
||||||
private Action? _endAction;
|
private Action? _endAction;
|
||||||
@@ -212,6 +214,30 @@ public class ProfilePanel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
ImGui.TextUnformatted(_selector.Selected!.Incognito);
|
ImGui.TextUnformatted(_selector.Selected!.Incognito);
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
|
ImGuiUtil.DrawFrameColumn("Priority");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
var priority = _newPriority ?? _selector.Selected!.Priority;
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(50);
|
||||||
|
if (ImGui.InputInt("##Priority", ref priority, 0, 0))
|
||||||
|
{
|
||||||
|
_newPriority = priority;
|
||||||
|
_changedProfile = _selector.Selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivatedAfterEdit() && _changedProfile != null)
|
||||||
|
{
|
||||||
|
_manager.SetPriority(_changedProfile, priority);
|
||||||
|
_newPriority = null;
|
||||||
|
_changedProfile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiComponents.HelpMarker("Profiles with a higher number here take precedence before profiles with a lower number.\n" +
|
||||||
|
"That means if two or more profiles affect same character, profile with higher priority will be applied to that character.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +371,36 @@ public class ProfilePanel
|
|||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(!_selector.IncognitoMode ? $"{character.ToNameWithoutOwnerName()}{character.TypeToString()}" : "Incognito");
|
ImGui.TextUnformatted(!_selector.IncognitoMode ? $"{character.ToNameWithoutOwnerName()}{character.TypeToString()}" : "Incognito");
|
||||||
|
|
||||||
|
var profiles = _manager.GetEnabledProfilesByActor(character).ToList();
|
||||||
|
if (profiles.Count > 1)
|
||||||
|
{
|
||||||
|
//todo: make helper
|
||||||
|
ImGui.SameLine();
|
||||||
|
if(profiles.Any(x => x.IsTemporary))
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Error);
|
||||||
|
ImGuiUtil.PrintIcon(FontAwesomeIcon.Lock);
|
||||||
|
}
|
||||||
|
else if (profiles[0] != _selector.Selected!)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Warning);
|
||||||
|
ImGuiUtil.PrintIcon(FontAwesomeIcon.ExclamationTriangle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, Constants.Colors.Info);
|
||||||
|
ImGuiUtil.PrintIcon(FontAwesomeIcon.Star);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
if (profiles.Any(x => x.IsTemporary))
|
||||||
|
ImGuiUtil.HoverTooltip("This character is being affected by temporary profile set by external plugin. This profile will not be applied!");
|
||||||
|
else
|
||||||
|
ImGuiUtil.HoverTooltip(profiles[0] != _selector.Selected! ? "Several profiles are trying to affect this character. This profile will not be applied!" :
|
||||||
|
"Several profiles are trying to affect this character. This profile is being applied.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user