mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Add wildcard support for automations
This commit is contained in:
parent
bf4673a1d9
commit
b384c8614a
3 changed files with 226 additions and 33 deletions
|
|
@ -155,6 +155,49 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
|
||||
foreach (var id in newSet.Identifiers)
|
||||
{
|
||||
// If the stored identifier uses a wildcard in the player name, it will not directly
|
||||
// be present in the ActorObjectManager dictionaries. Scan the live objects and
|
||||
// apply to any matching actors instead.
|
||||
if (!id.PlayerName.IsEmpty && id.PlayerName.ToString().Contains('*'))
|
||||
{
|
||||
var pattern = id.PlayerName.ToString();
|
||||
var regexPattern = System.Text.RegularExpressions.Regex.Escape(pattern).Replace("\\*", ".*");
|
||||
foreach (var (key, data2) in _objects)
|
||||
{
|
||||
if (key.Type != id.Type)
|
||||
continue;
|
||||
|
||||
var worldMatches = key.Type switch
|
||||
{
|
||||
IdentifierType.Player => key.HomeWorld == id.HomeWorld || key.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld || id.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld,
|
||||
IdentifierType.Owned => key.HomeWorld == id.HomeWorld || key.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld || id.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if (!worldMatches)
|
||||
continue;
|
||||
|
||||
if (!System.Text.RegularExpressions.Regex.IsMatch(key.PlayerName.ToString(), $"^{regexPattern}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
continue;
|
||||
|
||||
// Skip this actor if there's an exact-match automation set already enabled for it.
|
||||
if (_manager.EnabledSets.ContainsKey(key))
|
||||
continue;
|
||||
|
||||
// Apply to all actors represented by this key.
|
||||
foreach (var actor in data2.Objects)
|
||||
{
|
||||
var specificId = actor.GetIdentifier(_actors);
|
||||
if (_state.GetOrCreate(specificId, actor, out var state))
|
||||
{
|
||||
Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw);
|
||||
_state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
if (_objects.TryGetValue(id, out var data))
|
||||
{
|
||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
|
|
@ -318,30 +361,81 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
switch (identifier.Type)
|
||||
{
|
||||
case IdentifierType.Player:
|
||||
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||
if (TryGettingSetExactOrWildcard(identifier, out set))
|
||||
return true;
|
||||
|
||||
identifier = _actors.CreatePlayer(identifier.PlayerName, WorldId.AnyWorld);
|
||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||
if (TryGettingSetExactOrWildcard(identifier, out set))
|
||||
return true;
|
||||
|
||||
set = null;
|
||||
return false;
|
||||
case IdentifierType.Retainer:
|
||||
case IdentifierType.Npc:
|
||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||
return TryGettingSetExactOrWildcard(identifier, out set);
|
||||
case IdentifierType.Owned:
|
||||
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||
if (TryGettingSetExactOrWildcard(identifier, out set))
|
||||
return true;
|
||||
|
||||
identifier = _actors.CreateOwned(identifier.PlayerName, WorldId.AnyWorld, identifier.Kind, identifier.DataId);
|
||||
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||
if (TryGettingSetExactOrWildcard(identifier, out set))
|
||||
return true;
|
||||
|
||||
identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId);
|
||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||
return TryGettingSetExactOrWildcard(identifier, out set);
|
||||
default:
|
||||
set = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Try to get a set matching exactly or via wildcard pattern. </summary>
|
||||
private bool TryGettingSetExactOrWildcard(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set)
|
||||
{
|
||||
// First try exact match
|
||||
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||
return true;
|
||||
|
||||
// Then try wildcard matches
|
||||
foreach (var (key, value) in _manager.EnabledSets)
|
||||
{
|
||||
// Use wildcard-aware matching when the stored identifier contains a wildcard pattern in the name.
|
||||
if (!key.PlayerName.IsEmpty && key.PlayerName.ToString().Contains('*'))
|
||||
{
|
||||
var sameType = identifier.Type == key.Type;
|
||||
if (!sameType)
|
||||
continue;
|
||||
|
||||
var worldMatches = identifier.Type switch
|
||||
{
|
||||
Penumbra.GameData.Enums.IdentifierType.Player => identifier.HomeWorld == key.HomeWorld || identifier.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld || key.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld,
|
||||
Penumbra.GameData.Enums.IdentifierType.Owned => identifier.HomeWorld == key.HomeWorld || identifier.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld || key.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if (!worldMatches)
|
||||
continue;
|
||||
|
||||
var name = identifier.PlayerName.ToString();
|
||||
var pattern = key.PlayerName.ToString();
|
||||
var regexPattern = System.Text.RegularExpressions.Regex.Escape(pattern).Replace("\\*", ".*");
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(name, $"^{regexPattern}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
set = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (identifier.Matches(key))
|
||||
{
|
||||
set = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
set = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static int NewGearsetId = -1;
|
||||
|
||||
private void OnEquippedGearset(string name, int id, int prior, byte _, byte job)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using OtterGui.Filesystem;
|
|||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
|
|
@ -406,6 +407,60 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get an automation set that matches the given identifier, including wildcard patterns.
|
||||
/// First tries exact match, then tries wildcard patterns from enabled sets.
|
||||
/// </summary>
|
||||
public bool TryGetSetWithWildcard(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set)
|
||||
{
|
||||
// First try exact match
|
||||
if (_enabled.TryGetValue(identifier, out set))
|
||||
return true;
|
||||
|
||||
// Then try wildcard matching against all enabled sets
|
||||
foreach (var (_, enabledSet) in _enabled)
|
||||
{
|
||||
foreach (var setId in enabledSet.Identifiers)
|
||||
{
|
||||
// Use wildcard-aware matching when the stored identifier contains a wildcard pattern in the name.
|
||||
if (!setId.PlayerName.IsEmpty && setId.PlayerName.ToString().Contains('*'))
|
||||
{
|
||||
var sameType = identifier.Type == setId.Type;
|
||||
if (!sameType)
|
||||
continue;
|
||||
|
||||
var worldMatches = identifier.Type switch
|
||||
{
|
||||
Penumbra.GameData.Enums.IdentifierType.Player => identifier.HomeWorld == setId.HomeWorld || identifier.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld || setId.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld,
|
||||
Penumbra.GameData.Enums.IdentifierType.Owned => identifier.HomeWorld == setId.HomeWorld || identifier.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld || setId.HomeWorld == Penumbra.GameData.Structs.WorldId.AnyWorld,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if (!worldMatches)
|
||||
continue;
|
||||
|
||||
// Inline wildcard matching to avoid cross-namespace ambiguity.
|
||||
var name = identifier.PlayerName.ToString();
|
||||
var pattern = setId.PlayerName.ToString();
|
||||
var regexPattern = System.Text.RegularExpressions.Regex.Escape(pattern).Replace("\\*", ".*");
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(name, $"^{regexPattern}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
set = enabledSet;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (identifier.Matches(setId))
|
||||
{
|
||||
set = enabledSet;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
var file = _saveService.FileNames.AutomationFile;
|
||||
|
|
@ -611,35 +666,46 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
{
|
||||
IdentifierType.Player =>
|
||||
[
|
||||
identifier.CreatePermanent(),
|
||||
(IsWildcardName(identifier.PlayerName)
|
||||
? _actors.CreatePlayerUnchecked(identifier.PlayerName, identifier.HomeWorld).CreatePermanent()
|
||||
: identifier.CreatePermanent()),
|
||||
],
|
||||
IdentifierType.Retainer =>
|
||||
[
|
||||
_actors.CreateRetainer(identifier.PlayerName,
|
||||
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
|
||||
? ActorIdentifier.RetainerType.Mannequin
|
||||
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
|
||||
(IsWildcardName(identifier.PlayerName)
|
||||
? _actors.CreateRetainerUnchecked(identifier.PlayerName,
|
||||
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
|
||||
? ActorIdentifier.RetainerType.Mannequin
|
||||
: ActorIdentifier.RetainerType.Bell)
|
||||
: _actors.CreateRetainer(identifier.PlayerName,
|
||||
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
|
||||
? ActorIdentifier.RetainerType.Mannequin
|
||||
: ActorIdentifier.RetainerType.Bell)).CreatePermanent(),
|
||||
],
|
||||
IdentifierType.Npc => CreateNpcs(_actors, identifier),
|
||||
IdentifierType.Owned => CreateNpcs(_actors, identifier),
|
||||
_ => [],
|
||||
};
|
||||
|
||||
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
|
||||
{
|
||||
var name = manager.Data.ToName(identifier.Kind, identifier.DataId);
|
||||
var table = identifier.Kind switch
|
||||
{
|
||||
ObjectKind.BattleNpc => (IReadOnlyDictionary<NpcId, string>)manager.Data.BNpcs,
|
||||
ObjectKind.EventNpc => manager.Data.ENpcs,
|
||||
_ => new Dictionary<NpcId, string>(),
|
||||
};
|
||||
return table.Where(kvp => kvp.Value == name)
|
||||
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||
identifier.Kind, kvp.Key)).ToArray();
|
||||
}
|
||||
static bool IsWildcardName(ByteString name)
|
||||
=> name.ToString().Contains('*');
|
||||
}
|
||||
|
||||
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
|
||||
{
|
||||
var name = manager.Data.ToName(identifier.Kind, identifier.DataId);
|
||||
var table = identifier.Kind switch
|
||||
{
|
||||
ObjectKind.BattleNpc => (IReadOnlyDictionary<NpcId, string>)manager.Data.BNpcs,
|
||||
ObjectKind.EventNpc => manager.Data.ENpcs,
|
||||
_ => new Dictionary<NpcId, string>(),
|
||||
};
|
||||
return table.Where(kvp => kvp.Value == name)
|
||||
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||
identifier.Kind, kvp.Key)).ToArray();
|
||||
}
|
||||
|
||||
|
||||
private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _)
|
||||
{
|
||||
if (type is not DesignChanged.Type.Deleted)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Penumbra.GameData.DataContainers;
|
|||
using Penumbra.GameData.Gui;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.AutomationTab;
|
||||
|
||||
|
|
@ -64,20 +65,52 @@ public class IdentifierDrawer
|
|||
public bool CanSetOwned
|
||||
=> OwnedIdentifier.IsValid;
|
||||
|
||||
private static bool IsWildcardPattern(string? name)
|
||||
=> !string.IsNullOrEmpty(name) && name.Contains('*');
|
||||
|
||||
private static T Create<T>(bool wildcard, Func<T> uncheckedFactory, Func<T> checkedFactory)
|
||||
=> wildcard ? uncheckedFactory() : checkedFactory();
|
||||
|
||||
private void UpdateIdentifiers()
|
||||
{
|
||||
if (ByteString.FromString(_characterName, out var byteName))
|
||||
var isWildcard = IsWildcardPattern(_characterName);
|
||||
ByteString byteName = default;
|
||||
|
||||
// For wildcard patterns, use FromStringUnsafe to allow '*' characters
|
||||
if (isWildcard)
|
||||
byteName = ByteString.FromStringUnsafe(_characterName ?? string.Empty, false);
|
||||
else if (!ByteString.FromString(_characterName, out byteName))
|
||||
{
|
||||
PlayerIdentifier = _actors.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key);
|
||||
RetainerIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell);
|
||||
MannequinIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin);
|
||||
|
||||
if (_humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc)
|
||||
OwnedIdentifier = _actors.CreateOwned(byteName, _worldCombo.CurrentSelection.Key, _humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]);
|
||||
else
|
||||
OwnedIdentifier = ActorIdentifier.Invalid;
|
||||
PlayerIdentifier = ActorIdentifier.Invalid;
|
||||
RetainerIdentifier = ActorIdentifier.Invalid;
|
||||
MannequinIdentifier = ActorIdentifier.Invalid;
|
||||
OwnedIdentifier = ActorIdentifier.Invalid;
|
||||
NpcIdentifier = ActorIdentifier.Invalid;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create identifiers using a single helper to handle wildcard vs checked creation
|
||||
PlayerIdentifier = Create(isWildcard,
|
||||
() => _actors.CreatePlayerUnchecked(byteName, _worldCombo.CurrentSelection.Key),
|
||||
() => _actors.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key));
|
||||
|
||||
RetainerIdentifier = Create(isWildcard,
|
||||
() => _actors.CreateRetainerUnchecked(byteName, ActorIdentifier.RetainerType.Bell),
|
||||
() => _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell));
|
||||
|
||||
MannequinIdentifier = Create(isWildcard,
|
||||
() => _actors.CreateRetainerUnchecked(byteName, ActorIdentifier.RetainerType.Mannequin),
|
||||
() => _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin));
|
||||
|
||||
if (_humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc)
|
||||
OwnedIdentifier = Create(isWildcard,
|
||||
() => _actors.CreateIndividualUnchecked(IdentifierType.Owned, byteName, _worldCombo.CurrentSelection.Key.Id,
|
||||
_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]),
|
||||
() => _actors.CreateOwned(byteName, _worldCombo.CurrentSelection.Key, _humanNpcCombo.CurrentSelection.Kind,
|
||||
_humanNpcCombo.CurrentSelection.Ids[0]));
|
||||
else
|
||||
OwnedIdentifier = ActorIdentifier.Invalid;
|
||||
|
||||
NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc
|
||||
? _actors.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0])
|
||||
: ActorIdentifier.Invalid;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue