Added ability to apply profile to several characters
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CustomizePlus.Armatures.Data;
|
||||
using CustomizePlus.Core.Data;
|
||||
using CustomizePlus.Core.Extensions;
|
||||
@@ -32,7 +33,8 @@ public sealed class Profile : ISavable
|
||||
/* [Obsolete("To be removed in the future versions")]
|
||||
public LowerString CharacterName { get; set; } = LowerString.Empty;*/
|
||||
|
||||
public ActorIdentifier Character { get; set; } = ActorIdentifier.Invalid;
|
||||
//public ActorIdentifier Character { get; set; } = ActorIdentifier.Invalid;
|
||||
public List<ActorIdentifier> Characters { get; set; } = new();
|
||||
|
||||
public LowerString Name { get; set; } = LowerString.Empty;
|
||||
|
||||
@@ -78,7 +80,7 @@ public sealed class Profile : ISavable
|
||||
/// <param name="original"></param>
|
||||
public Profile(Profile original) : this()
|
||||
{
|
||||
Character = original.Character;
|
||||
Characters = original.Characters.ToList();
|
||||
|
||||
foreach (var template in original.Templates)
|
||||
{
|
||||
@@ -88,7 +90,7 @@ public sealed class Profile : ISavable
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Profile '{Name.Text.Incognify()}' on {Character.Incognito(null)} [{UniqueId}]";
|
||||
return $"Profile '{Name.Text.Incognify()}' on {string.Join(',', Characters.Select(x => x.Incognito(null)))} [{UniqueId}]";
|
||||
}
|
||||
|
||||
#region Serialization
|
||||
@@ -101,7 +103,7 @@ public sealed class Profile : ISavable
|
||||
["UniqueId"] = UniqueId,
|
||||
["CreationDate"] = CreationDate,
|
||||
["ModifiedDate"] = ModifiedDate,
|
||||
["Character"] = Character.ToJson(),
|
||||
["Characters"] = SerializeCharacters(),
|
||||
["Name"] = Name.Text,
|
||||
["Enabled"] = Enabled,
|
||||
["IsWriteProtected"] = IsWriteProtected,
|
||||
@@ -124,6 +126,16 @@ public sealed class Profile : ISavable
|
||||
return ret;
|
||||
}
|
||||
|
||||
private JArray SerializeCharacters()
|
||||
{
|
||||
var ret = new JArray();
|
||||
foreach (var character in Characters)
|
||||
{
|
||||
ret.Add(character.ToJson());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//Loading is in ProfileManager
|
||||
|
||||
@@ -15,7 +15,9 @@ public sealed class ProfileChanged() : EventWrapper<ProfileChanged.Type, Profile
|
||||
Deleted,
|
||||
Renamed,
|
||||
Toggled,
|
||||
ChangedCharacter,
|
||||
AddedCharacter,
|
||||
RemovedCharacter,
|
||||
//ChangedCharacter,
|
||||
AddedTemplate,
|
||||
RemovedTemplate,
|
||||
MovedTemplate,
|
||||
|
||||
@@ -101,30 +101,30 @@ public partial class ProfileManager : IDisposable
|
||||
var nameWordsCnt = characterName.Split(' ').Length;
|
||||
|
||||
if (_reverseNameDicts.TryGetID(ObjectKind.EventNpc, characterName, out var id))
|
||||
profile.Character = _actorManager.CreateNpc(ObjectKind.EventNpc, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateNpc(ObjectKind.EventNpc, new NpcId(id)));
|
||||
else if (_reverseNameDicts.TryGetID(ObjectKind.BattleNpc, characterName, out id))
|
||||
profile.Character = _actorManager.CreateNpc(ObjectKind.BattleNpc, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateNpc(ObjectKind.BattleNpc, new NpcId(id)));
|
||||
else if (_reverseNameDicts.TryGetID(ObjectKind.MountType, characterName, out id))
|
||||
{
|
||||
var currentPlayer = _actorManager.GetCurrentPlayer();
|
||||
profile.Character = _actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.MountType, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.MountType, new NpcId(id)));
|
||||
}
|
||||
else if (_reverseNameDicts.TryGetID(ObjectKind.Companion, characterName, out id))
|
||||
{
|
||||
var currentPlayer = _actorManager.GetCurrentPlayer();
|
||||
profile.Character = _actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.Companion, new NpcId(id));
|
||||
profile.Characters.Add(_actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.Companion, new NpcId(id)));
|
||||
}
|
||||
else if (nameWordsCnt == 2)
|
||||
profile.Character = _actorManager.CreatePlayer(ByteString.FromStringUnsafe(characterName, false), WorldId.AnyWorld);
|
||||
profile.Characters.Add(_actorManager.CreatePlayer(ByteString.FromStringUnsafe(characterName, false), WorldId.AnyWorld));
|
||||
else
|
||||
{
|
||||
_logger.Warning($"Unable to automatically migrate \"{profile.Name}\" to V5, unknown character name: {characterName}");
|
||||
_messageService.NotificationMessage($"Unable to detect character type for profile \"{profile.Name}\", please set character for this profile manually.", Dalamud.Interface.ImGuiNotification.NotificationType.Error);
|
||||
}
|
||||
|
||||
if (profile.Character.IsValid)
|
||||
if (profile.Characters.Count > 0)
|
||||
{
|
||||
_logger.Debug($"Upgraded profile \"{profile.Name}\" to V5: {characterName} -> {profile.Character}. Save queued.");
|
||||
_logger.Debug($"Upgraded profile \"{profile.Name}\" to V5: {characterName} -> {profile.Characters[0]}. Save queued.");
|
||||
_saveService.QueueSave(profile);
|
||||
}
|
||||
|
||||
@@ -135,14 +135,32 @@ public partial class ProfileManager : IDisposable
|
||||
{
|
||||
var profile = LoadProfileV4V5(obj);
|
||||
|
||||
var character = _actorManager.FromJson(obj["Character"] as JObject);
|
||||
if (obj["Characters"] is not JArray characterArray)
|
||||
return profile;
|
||||
|
||||
profile.Character = character;
|
||||
foreach(var characterObj in characterArray)
|
||||
{
|
||||
if (characterObj is not JObject characterObjCast)
|
||||
{
|
||||
//todo: warning
|
||||
continue;
|
||||
}
|
||||
|
||||
var character = _actorManager.FromJson(characterObjCast);
|
||||
|
||||
if(!character.IsValid)
|
||||
{
|
||||
//todo: warning
|
||||
continue;
|
||||
}
|
||||
|
||||
profile.Characters.Add(character);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
//V4 and V5 are mostly not different, so common loading logic is here
|
||||
//V4 and V5 are mostly the same, so common loading logic is here
|
||||
private Profile LoadProfileV4V5(JObject obj)
|
||||
{
|
||||
var creationDate = obj["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
|
||||
|
||||
@@ -177,15 +177,14 @@ public partial class ProfileManager : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change character associated with profile
|
||||
/// Add character to profile
|
||||
/// </summary>
|
||||
public void ChangeCharacter(Profile profile, ActorIdentifier actorIdentifier)
|
||||
public void AddCharacter(Profile profile, ActorIdentifier actorIdentifier)
|
||||
{
|
||||
if (!actorIdentifier.IsValid || actorIdentifier.MatchesIgnoringOwnership(profile.Character))
|
||||
if (!actorIdentifier.IsValid || profile.Characters.Any(x => actorIdentifier.MatchesIgnoringOwnership(x)) || profile.IsTemporary)
|
||||
return;
|
||||
|
||||
var oldCharacter = profile.Character;
|
||||
profile.Character = actorIdentifier;
|
||||
profile.Characters.Add(actorIdentifier);
|
||||
|
||||
//Called so all other active profiles for new character name get disabled
|
||||
//saving is performed there
|
||||
@@ -193,8 +192,28 @@ public partial class ProfileManager : IDisposable
|
||||
|
||||
SaveProfile(profile);
|
||||
|
||||
_logger.Debug($"Changed character for profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.ChangedCharacter, profile, oldCharacter);
|
||||
_logger.Debug($"Add character for profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.AddedCharacter, profile, actorIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete character from profile
|
||||
/// </summary>
|
||||
public void DeleteCharacter(Profile profile, ActorIdentifier actorIdentifier)
|
||||
{
|
||||
if (!actorIdentifier.IsValid || !profile.Characters.Any(x => actorIdentifier.MatchesIgnoringOwnership(x)) || profile.IsTemporary)
|
||||
return;
|
||||
|
||||
profile.Characters.Remove(actorIdentifier);
|
||||
|
||||
//Called so all other active profiles for new character name get disabled
|
||||
//saving is performed there
|
||||
//SetEnabled(profile, profile.Enabled, true); //todo
|
||||
|
||||
SaveProfile(profile);
|
||||
|
||||
_logger.Debug($"Removed character from profile {profile.UniqueId}.");
|
||||
_event.Invoke(ProfileChanged.Type.RemovedCharacter, profile, actorIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -235,11 +254,34 @@ public partial class ProfileManager : IDisposable
|
||||
{
|
||||
_logger.Debug($"Setting {profile} as enabled...");
|
||||
|
||||
foreach (var otherProfile in Profiles
|
||||
.Where(x => x.Character.MatchesIgnoringOwnership(profile.Character) && x != profile && x.Enabled && !x.IsTemporary))
|
||||
foreach (var otherProfile in Profiles)
|
||||
{
|
||||
_logger.Debug($"\t-> {otherProfile} disabled");
|
||||
SetEnabled(otherProfile, false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,12 +408,15 @@ public partial class ProfileManager : IDisposable
|
||||
|
||||
profile.Enabled = true;
|
||||
profile.ProfileType = ProfileType.Temporary;
|
||||
profile.Character = identifier.CreatePermanent(); //warn: identifier must not be AnyWorld or stuff will break!
|
||||
|
||||
var existingProfile = Profiles.FirstOrDefault(x => x.Character.MatchesIgnoringOwnership(profile.Character) && x.IsTemporary);
|
||||
var permanentIdentifier = identifier.CreatePermanent();
|
||||
profile.Characters.Clear();
|
||||
profile.Characters.Add(permanentIdentifier); //warn: identifier must not be AnyWorld or stuff will break!
|
||||
|
||||
var existingProfile = Profiles.FirstOrDefault(p => p.Characters.Count == 1 && p.Characters[0].MatchesIgnoringOwnership(permanentIdentifier) && p.IsTemporary);
|
||||
if (existingProfile != null)
|
||||
{
|
||||
_logger.Debug($"Temporary profile for {existingProfile.Character.Incognito(null)} already exists, removing...");
|
||||
_logger.Debug($"Temporary profile for {permanentIdentifier.Incognito(null)} already exists, removing...");
|
||||
Profiles.Remove(existingProfile);
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, existingProfile, null);
|
||||
}
|
||||
@@ -381,16 +426,19 @@ public partial class ProfileManager : IDisposable
|
||||
//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 {profile.Character}");
|
||||
_logger.Debug($"Added temporary profile for {permanentIdentifier}");
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileAdded, profile, null);
|
||||
}
|
||||
|
||||
public void RemoveTemporaryProfile(Profile profile)
|
||||
{
|
||||
if (!profile.IsTemporary)
|
||||
return;
|
||||
|
||||
if (!Profiles.Remove(profile))
|
||||
throw new ProfileNotFoundException();
|
||||
|
||||
_logger.Debug($"Removed temporary profile for {profile.Character.Incognito(null)}");
|
||||
_logger.Debug($"Removed temporary profile for {profile.Characters[0].Incognito(null)}");
|
||||
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null);
|
||||
}
|
||||
@@ -409,7 +457,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!actor.Identifier(_actorManager, out var identifier))
|
||||
throw new ActorNotFoundException();
|
||||
|
||||
var profile = Profiles.FirstOrDefault(x => x.Character == identifier && x.IsTemporary);
|
||||
var profile = Profiles.FirstOrDefault(x => x.Characters[0] == identifier && x.IsTemporary);
|
||||
if (profile == null)
|
||||
throw new ProfileNotFoundException();
|
||||
|
||||
@@ -426,7 +474,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!actorIdentifier.IsValid)
|
||||
return null;
|
||||
|
||||
var query = Profiles.Where(x => x.Character.MatchesIgnoringOwnership(actorIdentifier) && !x.IsTemporary);
|
||||
var query = Profiles.Where(p => p.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier)) && !p.IsTemporary);
|
||||
if (enabledOnly)
|
||||
query = query.Where(x => x.Enabled);
|
||||
|
||||
@@ -475,7 +523,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (actorIdentifier.Type == IdentifierType.Owned && !actorIdentifier.IsOwnedByLocalPlayer())
|
||||
return false;
|
||||
|
||||
return profile.Character.MatchesIgnoringOwnership(actorIdentifier);
|
||||
return profile.Characters.Any(x => x.MatchesIgnoringOwnership(actorIdentifier));
|
||||
}
|
||||
|
||||
if (_templateEditorManager.IsEditorActive && _templateEditorManager.EditorProfile.Enabled && IsProfileAppliesToCurrentActor(_templateEditorManager.EditorProfile))
|
||||
@@ -585,7 +633,7 @@ public partial class ProfileManager : IDisposable
|
||||
if (!Profiles.Remove(profile))
|
||||
return;
|
||||
|
||||
_logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.Character.Incognito(null)}");
|
||||
_logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.Characters[0].Incognito(null)}");
|
||||
|
||||
_event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user