Add viera ear flags

This commit is contained in:
Ottermandias 2025-08-08 15:38:51 +02:00
parent 0f98fac157
commit 00d550f4fe
21 changed files with 245 additions and 21 deletions

View file

@ -262,6 +262,14 @@ public class StateApplier(
_visor.SetVisorState(actor.Model, value);
return;
}
case MetaIndex.EarState:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
{
var model = actor.Model;
model.VieraEarsVisible = value;
}
return;
}
}
@ -402,6 +410,7 @@ public class StateApplier(
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
ChangeMetaState(actors, MetaIndex.EarState, state.ModelData.AreEarsVisible());
ChangeCrests(actors, state.ModelData.CrestVisibility);
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters);
ChangeMaterialValues(actors, state.Materials);

View file

@ -189,8 +189,9 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
public const int MetaVisorState = MetaHatState + 1;
public const int MetaWeaponState = MetaVisorState + 1;
public const int MetaModelId = MetaWeaponState + 1;
public const int MetaEarState = MetaModelId + 1;
public const int CrestHead = MetaModelId + 1;
public const int CrestHead = MetaEarState + 1;
public const int CrestBody = CrestHead + 1;
public const int CrestOffhand = CrestBody + 1;
@ -300,6 +301,7 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
MetaHatState => MetaFlag.HatState,
MetaVisorState => MetaFlag.VisorState,
MetaWeaponState => MetaFlag.WeaponState,
MetaEarState => MetaFlag.EarState,
MetaModelId => true,
CrestHead => CrestFlag.Head,

View file

@ -9,6 +9,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.GameData;
using Penumbra.GameData.DataContainers;
@ -39,6 +40,7 @@ public class StateListener : IDisposable
private readonly WeaponLoading _weaponLoading;
private readonly HeadGearVisibilityChanged _headGearVisibility;
private readonly VisorStateChanged _visorState;
private readonly VieraEarStateChanged _vieraEarState;
private readonly WeaponVisibilityChanged _weaponVisibility;
private readonly StateFinalized _stateFinalized;
private readonly AutoDesignApplier _autoDesignApplier;
@ -62,7 +64,7 @@ public class StateListener : IDisposable
WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier,
FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ActorObjectManager objects,
GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition,
CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized)
CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized, VieraEarStateChanged vieraEarState)
{
_manager = manager;
_items = items;
@ -88,6 +90,7 @@ public class StateListener : IDisposable
_crestService = crestService;
_bonusSlotUpdating = bonusSlotUpdating;
_stateFinalized = stateFinalized;
_vieraEarState = vieraEarState;
Subscribe();
}
@ -266,7 +269,7 @@ public class StateListener : IDisposable
private void OnGearsetDataLoaded(Actor actor, Model model)
{
if (!actor.Valid || (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart))
if (!actor.Valid || _condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
// ensure actor and state are valid.
@ -710,6 +713,44 @@ public class StateListener : IDisposable
}
}
/// <summary> Handle visor state changes made by the game. </summary>
private void OnVieraEarChange(Actor actor, ref bool value)
{
// Value is inverted compared to our own handling.
// Skip updates when in customize update.
if (ChangeCustomizeService.InUpdate.InMethod)
return;
if (!actor.IsCharacter)
return;
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
if (!actor.Identifier(_actors, out var identifier))
return;
if (!_manager.TryGetValue(identifier, out var state))
return;
// Update visor base state.
if (state.BaseData.SetEarsVisible(!value))
{
// if base state changed, either overwrite the actual value if we have fixed values,
// or overwrite the stored model state with the new one.
if (state.Sources[MetaIndex.EarState].IsFixed())
value = !state.ModelData.AreEarsVisible();
else
_manager.ChangeMetaState(state, MetaIndex.EarState, !value, ApplySettings.Game);
}
else
{
// if base state did not change, overwrite the value with the model state one.
value = !state.ModelData.AreEarsVisible();
}
}
/// <summary> Handle Hat Visibility changes. These act on the game object. </summary>
private void OnHeadGearVisibilityChange(Actor actor, ref bool value)
{
@ -802,6 +843,7 @@ public class StateListener : IDisposable
_movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener);
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
_vieraEarState.Subscribe(OnVieraEarChange, VieraEarStateChanged.Priority.StateListener);
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
_weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener);
_changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener);
@ -820,6 +862,7 @@ public class StateListener : IDisposable
_movedEquipment.Unsubscribe(OnMovedEquipment);
_weaponLoading.Unsubscribe(OnWeaponLoading);
_visorState.Unsubscribe(OnVisorChange);
_vieraEarState.Unsubscribe(OnVieraEarChange);
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
_weaponVisibility.Unsubscribe(OnWeaponVisibilityChange);
_changeCustomizeService.Unsubscribe(OnCustomizeChange);

View file

@ -158,6 +158,7 @@ public sealed class StateManager(
// Visor state is a flag on the game object, but we can see the actual state on the draw object.
ret.SetVisor(VisorService.GetVisorState(model));
ret.SetEarsVisible(model.VieraEarsVisible);
foreach (var slot in CrestExtensions.AllRelevantSet)
ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot));
@ -186,7 +187,7 @@ public sealed class StateManager(
off = actor.GetOffhand();
FistWeaponHack(ref ret, ref main, ref off);
ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
ret.SetEarsVisible(actor.ShowVieraEars);
foreach (var slot in CrestExtensions.AllRelevantSet)
ret.SetCrest(slot, actor.GetCrest(slot));