More work on profile character assignment rewrite.
Added ability to apply profile to any currently logged in character Functional UI for player character, retainers and mannequins Almost completely switched to using ActorIdentifier instead of character name Migration code for ActorIdentifier instead of character names IPC is not functional for now (see todos)
This commit is contained in:
@@ -21,7 +21,7 @@ namespace CustomizePlus.Profiles.Data;
|
||||
/// </summary>
|
||||
public sealed class Profile : ISavable
|
||||
{
|
||||
public const int Version = 4;
|
||||
public const int Version = 5;
|
||||
|
||||
private static int _nextGlobalId;
|
||||
|
||||
@@ -29,10 +29,11 @@ public sealed class Profile : ISavable
|
||||
|
||||
public List<Armature> Armatures = new();
|
||||
|
||||
[Obsolete("To be removed")]
|
||||
[Obsolete("To be removed in the future versions")]
|
||||
public LowerString CharacterName { get; set; } = LowerString.Empty;
|
||||
|
||||
public ActorIdentifier Character { get; set; } = ActorIdentifier.Invalid;
|
||||
public bool ApplyToCurrentlyActiveCharacter { get; set; }
|
||||
|
||||
public LowerString Name { get; set; } = LowerString.Empty;
|
||||
|
||||
@@ -59,10 +60,10 @@ public sealed class Profile : ISavable
|
||||
/// </summary>
|
||||
public bool IsTemporary => ProfileType == ProfileType.Temporary;
|
||||
|
||||
/// <summary>
|
||||
/* /// <summary>
|
||||
/// Identificator specifying specific actor this profile applies to, only works for temporary profiles
|
||||
/// </summary>
|
||||
public ActorIdentifier TemporaryActor { get; set; } = ActorIdentifier.Invalid;
|
||||
public ActorIdentifier TemporaryActor { get; set; } = ActorIdentifier.Invalid;*/
|
||||
|
||||
public string Incognito
|
||||
=> UniqueId.ToString()[..8];
|
||||
@@ -78,8 +79,9 @@ public sealed class Profile : ISavable
|
||||
/// <param name="original"></param>
|
||||
public Profile(Profile original) : this()
|
||||
{
|
||||
CharacterName = original.CharacterName;
|
||||
Character = original.Character;
|
||||
LimitLookupToOwnedObjects = original.LimitLookupToOwnedObjects;
|
||||
ApplyToCurrentlyActiveCharacter = original.ApplyToCurrentlyActiveCharacter;
|
||||
|
||||
foreach (var template in original.Templates)
|
||||
{
|
||||
@@ -89,7 +91,7 @@ public sealed class Profile : ISavable
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Profile '{Name.Text.Incognify()}' on {CharacterName.Text.Incognify()} [{UniqueId}]";
|
||||
return $"Profile '{Name.Text.Incognify()}' on {Character.Incognito(null)} [{UniqueId}]";
|
||||
}
|
||||
|
||||
#region Serialization
|
||||
@@ -103,7 +105,8 @@ public sealed class Profile : ISavable
|
||||
["CreationDate"] = CreationDate,
|
||||
["ModifiedDate"] = ModifiedDate,
|
||||
["CharacterName"] = CharacterName.Text,
|
||||
//["Character"] = Character.ToJson(),
|
||||
["Character"] = Character.ToJson(),
|
||||
["ApplyToCurrentlyActiveCharacter"] = ApplyToCurrentlyActiveCharacter,
|
||||
["Name"] = Name.Text,
|
||||
["LimitLookupToOwnedObjects"] = LimitLookupToOwnedObjects,
|
||||
["Enabled"] = Enabled,
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed class ProfileChanged() : EventWrapper<ProfileChanged.Type, Profile
|
||||
Deleted,
|
||||
Renamed,
|
||||
Toggled,
|
||||
ChangedCharacterName,
|
||||
ChangedCharacter,
|
||||
AddedTemplate,
|
||||
RemovedTemplate,
|
||||
MovedTemplate,
|
||||
@@ -23,6 +23,7 @@ public sealed class ProfileChanged() : EventWrapper<ProfileChanged.Type, Profile
|
||||
ReloadedAll,
|
||||
WriteProtection,
|
||||
LimitLookupToOwnedChanged,
|
||||
ApplyToCurrentlyActiveCharacterChanged,
|
||||
ChangedDefaultProfile,
|
||||
TemporaryProfileAdded,
|
||||
TemporaryProfileDeleted,
|
||||
|
||||
@@ -81,46 +81,44 @@ public partial class ProfileManager : IDisposable
|
||||
return version switch
|
||||
{
|
||||
//Ignore everything below v4
|
||||
// 4 => LoadV4(obj),
|
||||
// 5 => LoadV5(obj),
|
||||
4 => LoadV5(obj),
|
||||
4 => LoadV4(obj),
|
||||
5 => LoadV5(obj),
|
||||
_ => throw new Exception("The profile to be loaded has no valid Version."),
|
||||
};
|
||||
}
|
||||
|
||||
private Profile LoadV4(JObject obj)
|
||||
{
|
||||
var characterName = new LowerString(obj["CharacterName"]?.ToObject<string>()?.Trim() ?? throw new ArgumentNullException("CharacterName"));
|
||||
var profile = LoadProfileV4V5(obj);
|
||||
|
||||
ByteString.FromString(characterName, out var nameByteString);
|
||||
var character = _actorManager.CreatePlayer(nameByteString, WorldId.AnyWorld); //todo: detect type
|
||||
|
||||
obj["Character"] = character.ToJson();
|
||||
|
||||
var profile = LoadV5(obj);
|
||||
|
||||
profile.ModifiedDate = DateTimeOffset.UtcNow;
|
||||
_saveService.ImmediateSave(profile);
|
||||
profile.CharacterName = new LowerString(obj["CharacterName"]?.ToObject<string>()?.Trim() ?? throw new ArgumentNullException("CharacterName"));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
private Profile LoadV5(JObject obj)
|
||||
{
|
||||
var profile = LoadProfileV4V5(obj);
|
||||
|
||||
var character = _actorManager.FromJson(obj["Character"] as JObject);
|
||||
|
||||
profile.Character = character;
|
||||
profile.ApplyToCurrentlyActiveCharacter = obj["ApplyToCurrentlyActiveCharacter"]?.ToObject<bool>() ?? false;
|
||||
profile.CharacterName = new LowerString(obj["CharacterName"]?.ToObject<string>()?.Trim() ?? throw new ArgumentNullException("CharacterName")); //temp
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
//V4 and V5 are mostly not different, so common loading logic is here
|
||||
private Profile LoadProfileV4V5(JObject obj)
|
||||
{
|
||||
var creationDate = obj["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
|
||||
|
||||
/*var character = _actorManager.FromJson(obj["Character"] as JObject);
|
||||
|
||||
if (!character.IsValid)
|
||||
throw new ArgumentException("Character");*/
|
||||
|
||||
var profile = new Profile()
|
||||
{
|
||||
CreationDate = creationDate,
|
||||
UniqueId = obj["UniqueId"]?.ToObject<Guid>() ?? throw new ArgumentNullException("UniqueId"),
|
||||
Name = new LowerString(obj["Name"]?.ToObject<string>()?.Trim() ?? throw new ArgumentNullException("Name")),
|
||||
//Character = character,
|
||||
CharacterName = new LowerString(obj["CharacterName"]?.ToObject<string>()?.Trim() ?? throw new ArgumentNullException("CharacterName")),
|
||||
LimitLookupToOwnedObjects = obj["LimitLookupToOwnedObjects"]?.ToObject<bool>() ?? throw new ArgumentNullException("LimitLookupToOwnedObjects"),
|
||||
Enabled = obj["Enabled"]?.ToObject<bool>() ?? throw new ArgumentNullException("Enabled"),
|
||||
ModifiedDate = obj["ModifiedDate"]?.ToObject<DateTimeOffset>() ?? creationDate,
|
||||
|
||||
@@ -168,26 +168,24 @@ public partial class ProfileManager : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change character name for profile
|
||||
/// Change character associated with profile
|
||||
/// </summary>
|
||||
public void ChangeCharacterName(Profile profile, string newName)
|
||||
public void ChangeCharacter(Profile profile, ActorIdentifier actorIdentifier)
|
||||
{
|
||||
newName = newName.Trim();
|
||||
|
||||
var oldName = profile.CharacterName.Text;
|
||||
if (oldName == newName)
|
||||
if (!actorIdentifier.IsValid || actorIdentifier.Matches(profile.Character))
|
||||
return;
|
||||
|
||||
profile.CharacterName = newName;
|
||||
var oldCharacter = profile.Character;
|
||||
profile.Character = actorIdentifier;
|
||||
|
||||
//Called so all other active profiles for new character name get disabled
|
||||
//saving is performed there
|
||||
SetEnabled(profile, profile.Enabled, true);
|
||||
//SetEnabled(profile, profile.Enabled, true); //todo
|
||||
|
||||
SaveProfile(profile);
|
||||
|
||||
_logger.Debug($"Changed character name for profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.ChangedCharacterName, profile, oldName);
|
||||
_logger.Debug($"Changed character for profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.ChangedCharacter, profile, oldCharacter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -229,7 +227,7 @@ public partial class ProfileManager : IDisposable
|
||||
_logger.Debug($"Setting {profile} as enabled...");
|
||||
|
||||
foreach (var otherProfile in Profiles
|
||||
.Where(x => x.CharacterName == profile.CharacterName && x != profile && x.Enabled && !x.IsTemporary))
|
||||
.Where(x => x.Character.Matches(profile.Character) && x != profile && x.Enabled && !x.IsTemporary))
|
||||
{
|
||||
_logger.Debug($"\t-> {otherProfile} disabled");
|
||||
SetEnabled(otherProfile, false);
|
||||
@@ -268,6 +266,18 @@ public partial class ProfileManager : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public void SetApplyToCurrentlyActiveCharacter(Profile profile, bool value)
|
||||
{
|
||||
if (profile.ApplyToCurrentlyActiveCharacter != value)
|
||||
{
|
||||
profile.ApplyToCurrentlyActiveCharacter = value;
|
||||
|
||||
SaveProfile(profile);
|
||||
|
||||
_event.Invoke(ProfileChanged.Type.ApplyToCurrentlyActiveCharacterChanged, profile, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteTemplate(Profile profile, int templateIndex)
|
||||
{
|
||||
_logger.Debug($"Deleting template #{templateIndex} from {profile}...");
|
||||
@@ -342,6 +352,7 @@ public partial class ProfileManager : IDisposable
|
||||
_event.Invoke(ProfileChanged.Type.ChangedDefaultProfile, profile, previousProfile);
|
||||
}
|
||||
|
||||
//warn: temporary profile system does not support any world identifiers
|
||||
public void AddTemporaryProfile(Profile profile, Actor actor/*, Template template*/)
|
||||
{
|
||||
if (!actor.Identifier(_actorManager, out var identifier))
|
||||
@@ -349,14 +360,13 @@ public partial class ProfileManager : IDisposable
|
||||
|
||||
profile.Enabled = true;
|
||||
profile.ProfileType = ProfileType.Temporary;
|
||||
profile.TemporaryActor = identifier;
|
||||
profile.CharacterName = identifier.ToNameWithoutOwnerName();
|
||||
profile.Character = identifier;
|
||||
profile.LimitLookupToOwnedObjects = false;
|
||||
|
||||
var existingProfile = Profiles.FirstOrDefault(x => x.CharacterName.Lower == profile.CharacterName.Lower && x.IsTemporary);
|
||||
var existingProfile = Profiles.FirstOrDefault(x => x.Character.Matches(profile.Character) && x.IsTemporary);
|
||||
if (existingProfile != null)
|
||||
{
|
||||
_logger.Debug($"Temporary profile for {existingProfile.CharacterName} already exists, removing...");
|
||||
_logger.Debug($"Temporary profile for {existingProfile.Character.Incognito(null)} already exists, removing...");
|
||||
Profiles.Remove(existingProfile);
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, existingProfile, null);
|
||||
}
|
||||
@@ -375,7 +385,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!Profiles.Remove(profile))
|
||||
throw new ProfileNotFoundException();
|
||||
|
||||
_logger.Debug($"Removed temporary profile for {profile.CharacterName}");
|
||||
_logger.Debug($"Removed temporary profile for {profile.Character.Incognito(null)}");
|
||||
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null);
|
||||
}
|
||||
@@ -394,7 +404,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!actor.Identifier(_actorManager, out var identifier))
|
||||
throw new ActorNotFoundException();
|
||||
|
||||
var profile = Profiles.FirstOrDefault(x => x.TemporaryActor == identifier && x.IsTemporary);
|
||||
var profile = Profiles.FirstOrDefault(x => x.Character == identifier && x.IsTemporary);
|
||||
if (profile == null)
|
||||
throw new ProfileNotFoundException();
|
||||
|
||||
@@ -402,17 +412,16 @@ public partial class ProfileManager : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return profile by character name, does not return temporary profiles
|
||||
/// Return profile by actor identifier, does not return temporary profiles.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="enabledOnly"></param>
|
||||
/// <returns></returns>
|
||||
public Profile? GetProfileByCharacterName(string name, bool enabledOnly = false)
|
||||
public Profile? GetProfileByActor(Actor actor, bool enabledOnly = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
var actorIdentifier = actor.GetIdentifier(_actorManager);
|
||||
|
||||
if (!actorIdentifier.IsValid)
|
||||
return null;
|
||||
|
||||
var query = Profiles.Where(x => x.CharacterName == name);
|
||||
var query = Profiles.Where(x => x.Character.Matches(actorIdentifier) && !x.IsTemporary);
|
||||
if (enabledOnly)
|
||||
query = query.Where(x => x.Enabled);
|
||||
|
||||
@@ -447,7 +456,16 @@ public partial class ProfileManager : IDisposable
|
||||
if (profile == DefaultProfile)
|
||||
return false;
|
||||
|
||||
return profile.CharacterName.Text == name &&
|
||||
if (profile.ApplyToCurrentlyActiveCharacter)
|
||||
{
|
||||
if (_objectManager.IsInLobby)
|
||||
return true;
|
||||
|
||||
var currentPlayer = _actorManager.GetCurrentPlayer();
|
||||
return currentPlayer.IsValid && _actorManager.GetCurrentPlayer().Matches(actorIdentifier);
|
||||
}
|
||||
|
||||
return (profile.CharacterName.Text == name || profile.Character.Matches(actorIdentifier)) &&
|
||||
(!profile.LimitLookupToOwnedObjects ||
|
||||
(actorIdentifier.Type == IdentifierType.Owned &&
|
||||
actorIdentifier.PlayerName == _actorManager.GetCurrentPlayer().PlayerName));
|
||||
@@ -458,8 +476,19 @@ public partial class ProfileManager : IDisposable
|
||||
|
||||
foreach (var profile in Profiles)
|
||||
{
|
||||
if (IsProfileAppliesToCurrentActor(profile) && profile.Enabled)
|
||||
yield return profile;
|
||||
if(IsProfileAppliesToCurrentActor(profile))
|
||||
{
|
||||
//todo: temp for migrations to v5
|
||||
if (!profile.Character.IsValid)
|
||||
{
|
||||
_logger.Warning($"No character for profile {profile}, but character has been found as: {actorIdentifier}, will set.");
|
||||
profile.Character = actorIdentifier;
|
||||
_saveService.QueueSave(profile);
|
||||
}
|
||||
|
||||
if (profile.Enabled)
|
||||
yield return profile;
|
||||
}
|
||||
}
|
||||
|
||||
if (DefaultProfile != null &&
|
||||
@@ -553,7 +582,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!Profiles.Remove(profile))
|
||||
return;
|
||||
|
||||
_logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.CharacterName}");
|
||||
_logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.Character.Incognito(null)}");
|
||||
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user