This commit is contained in:
Ottermandias 2023-07-08 21:40:22 +02:00
parent 8526ce4f33
commit 0b22dd9760
17 changed files with 877 additions and 535 deletions

View file

@ -2,6 +2,7 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;
@ -76,7 +77,7 @@ public partial class GlamourerIpc
continue;
}
_stateManager.ApplyDesign(design, state);
_stateManager.ApplyDesign(design, state, StateChanged.Source.Ipc);
}
}
}

View file

@ -115,6 +115,17 @@ public class AutoDesignApplier : IDisposable
_state.ReapplyState(actor);
}
public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state)
{
if (!_config.EnableAutoDesigns)
return;
if (!GetPlayerSet(identifier, out var set))
return;
Reduce(actor, state, set, false);
}
public void Reduce(Actor actor, ActorIdentifier identifier, ActorState state)
{
if (!_config.EnableAutoDesigns)

View file

@ -130,8 +130,8 @@ public sealed class Design : DesignBase, ISavable
if (design.LastEdit < creationDate)
design.LastEdit = creationDate;
LoadEquip(items, json["Equipment"], design, design.Name);
LoadCustomize(customizations, json["Customize"], design, design.Name);
LoadEquip(items, json["Equipment"], design, design.Name);
LoadMods(json["Mods"], design);
return design;
}

View file

@ -25,7 +25,7 @@ public class DesignBase
DesignData = clone.DesignData;
ApplyCustomize = clone.ApplyCustomize & CustomizeFlagExtensions.All;
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
_designFlags = clone._designFlags & (DesignFlags) 0x0F;
_designFlags = clone._designFlags & (DesignFlags)0x0F;
}
internal DesignData DesignData = new();
@ -177,7 +177,6 @@ public class DesignBase
};
var ret = new JObject();
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
{
var item = DesignData.Item(slot);
@ -188,6 +187,7 @@ public class DesignBase
ret["Hat"] = new QuadBool(DesignData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply");
ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply");
ret["Array"] = DesignData.WriteEquipmentBytesBase64();
return ret;
}
@ -213,6 +213,7 @@ public class DesignBase
["Value"] = DesignData.IsWet(),
["Apply"] = DoApplyWetness(),
};
ret["Array"] = DesignData.Customize.WriteBase64();
return ret;
}
@ -234,8 +235,8 @@ public class DesignBase
private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json)
{
var ret = new DesignBase(items);
LoadEquip(items, json["Equipment"], ret, "Temporary Design");
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design");
LoadEquip(items, json["Equipment"], ret, "Temporary Design");
return ret;
}
@ -249,6 +250,13 @@ public class DesignBase
return;
}
if (!design.DesignData.IsHuman)
{
var textArray = equip["Array"]?.ToObject<string>() ?? string.Empty;
design.DesignData.SetEquipmentBytesFromBase64(textArray);
return;
}
static (uint, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item)
{
var id = item?["ItemId"]?.ToObject<uint>() ?? ItemManager.NothingId(slot);
@ -314,6 +322,7 @@ public class DesignBase
if (json == null)
{
design.DesignData.ModelId = 0;
design.DesignData.IsHuman = true;
design.DesignData.Customize = Customize.Default;
Glamourer.Chat.NotificationMessage("The loaded design does not contain any customization data, reset to default.", "Warning",
NotificationType.Warning);
@ -326,8 +335,18 @@ public class DesignBase
Glamourer.Chat.NotificationMessage($"{msg} ({name})", "Warning", NotificationType.Warning);
}
var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse);
design.DesignData.SetIsWet(wetness.ForcedValue);
design.SetApplyWetness(wetness.Enabled);
design.DesignData.ModelId = json["ModelId"]?.ToObject<uint>() ?? 0;
PrintWarning(customizations.ValidateModelId(design.DesignData.ModelId, out design.DesignData.ModelId));
PrintWarning(customizations.ValidateModelId(design.DesignData.ModelId, out design.DesignData.ModelId, out design.DesignData.IsHuman));
if (!design.DesignData.IsHuman)
{
var arrayText = json["Array"]?.ToObject<string>() ?? string.Empty;
design.DesignData.Customize.LoadBase64(arrayText);
return;
}
var race = (Race)(json[CustomizeIndex.Race.ToString()]?["Value"]?.ToObject<byte>() ?? 0);
var clan = (SubRace)(json[CustomizeIndex.Clan.ToString()]?["Value"]?.ToObject<byte>() ?? 0);
@ -352,10 +371,6 @@ public class DesignBase
design.DesignData.Customize[idx] = data;
design.SetApplyCustomize(idx, apply);
}
var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse);
design.DesignData.SetIsWet(wetness.ForcedValue);
design.SetApplyWetness(wetness.Enabled);
}
public void MigrateBase64(ItemManager items, string base64)

View file

@ -1,4 +1,5 @@
using System;
using System.Buffers.Text;
using System.Runtime.CompilerServices;
using Glamourer.Customization;
using Glamourer.Services;
@ -33,6 +34,7 @@ public unsafe struct DesignData
private FullEquipType _typeMainhand;
private FullEquipType _typeOffhand;
private byte _states;
public bool IsHuman = true;
public DesignData()
{ }
@ -202,14 +204,49 @@ public unsafe struct DesignData
SetStain(EquipSlot.OffHand, 0);
}
public void LoadNonHuman(uint modelId, Customize customize, byte* equipData)
public bool LoadNonHuman(uint modelId, Customize customize, byte* equipData)
{
ModelId = modelId;
IsHuman = false;
Customize.Load(customize);
fixed (byte* ptr = _equipmentBytes)
{
MemoryUtility.MemCpyUnchecked(ptr, equipData, 40);
MemoryUtility.MemSet(ptr + 40, 0, 8);
}
SetHatVisible(true);
SetWeaponVisible(true);
SetVisor(false);
fixed (uint* ptr = _itemIds)
{
MemoryUtility.MemSet(ptr, 0, 12 * 4);
}
fixed (ushort* ptr = _iconIds)
{
MemoryUtility.MemSet(ptr, 0, 12 * 2);
}
_secondaryMainhand = 0;
_secondaryOffhand = 0;
_typeMainhand = FullEquipType.Unknown;
_typeOffhand = FullEquipType.Unknown;
_nameHead = string.Empty;
_nameBody = string.Empty;
_nameHands = string.Empty;
_nameLegs = string.Empty;
_nameFeet = string.Empty;
_nameEars = string.Empty;
_nameNeck = string.Empty;
_nameWrists = string.Empty;
_nameRFinger = string.Empty;
_nameLFinger = string.Empty;
_nameMainhand = string.Empty;
_nameOffhand = string.Empty;
return true;
}
public readonly byte[] GetCustomizeBytes()
@ -234,6 +271,32 @@ public unsafe struct DesignData
return ret;
}
public nint GetEquipmentPtr()
{
fixed (byte* ptr = _equipmentBytes)
{
return (nint)ptr;
}
}
public bool SetEquipmentBytesFromBase64(string base64)
{
fixed (byte* dataPtr = _equipmentBytes)
{
var data = new Span<byte>(dataPtr, 40);
return Convert.TryFromBase64String(base64, data, out var written) && written == 40;
}
}
public string WriteEquipmentBytesBase64()
{
fixed (byte* dataPtr = _equipmentBytes)
{
var data = new ReadOnlySpan<byte>(dataPtr, 40);
return Convert.ToBase64String(data);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T>

View file

@ -19,6 +19,12 @@ public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateC
{
public enum Type
{
/// <summary> A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] </summary>
Model,
/// <summary> A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] </summary>
EntireCustomize,
/// <summary> A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
Customize,
@ -31,6 +37,12 @@ public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateC
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Stain,
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
Design,
/// <summary> A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. </summary>
Reset,
/// <summary> A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Other,
}

View file

@ -1,22 +1,24 @@
using System;
using System.Numerics;
using System.Xml.Linq;
using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.ActorTab;
@ -26,8 +28,11 @@ public class ActorPanel
private readonly StateManager _stateManager;
private readonly CustomizationDrawer _customizationDrawer;
private readonly EquipmentDrawer _equipmentDrawer;
private readonly HumanModelList _humans;
private readonly IdentifierService _identification;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly Configuration _config;
private readonly DesignConverter _converter;
private readonly ObjectManager _objects;
private ActorIdentifier _identifier;
private string _actorName = string.Empty;
@ -36,14 +41,18 @@ public class ActorPanel
private ActorState? _state;
public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer,
EquipmentDrawer equipmentDrawer, HumanModelList humans, IdentifierService identification)
EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier,
Configuration config, DesignConverter converter, ObjectManager objects)
{
_selector = selector;
_stateManager = stateManager;
_customizationDrawer = customizationDrawer;
_equipmentDrawer = equipmentDrawer;
_humans = humans;
_identification = identification;
_autoDesignApplier = autoDesignApplier;
_config = config;
_converter = converter;
_objects = objects;
}
public void Draw()
@ -91,6 +100,28 @@ public class ActorPanel
return (_selector.IncognitoMode ? _identifier.Incognito(null) : _identifier.ToString(), Actor.Null);
}
private unsafe void DrawPanel()
{
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state))
return;
ApplyClipboardButton();
ImGui.SameLine();
CopyToClipboardButton();
ImGui.SameLine();
DrawApplyToSelf();
ImGui.SameLine();
DrawApplyToTarget();
RevertButtons();
if (_state.ModelData.IsHuman)
DrawHumanPanel();
else
DrawMonsterPanel();
}
private void DrawHumanPanel()
{
if (_customizationDrawer.Draw(_state!.ModelData.Customize, false))
@ -191,113 +222,85 @@ public class ActorPanel
_stateManager.TurnHuman(_state, StateChanged.Source.Manual);
}
private unsafe void DrawPanel()
private void ApplyClipboardButton()
{
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state))
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
"Try to apply a design from your clipboard.", false, true))
return;
if (_humans.IsHuman(_state.ModelData.ModelId))
DrawHumanPanel();
else
DrawMonsterPanel();
}
private unsafe void RevertButton()
try
{
//if (ImGui.Button("Revert"))
// _activeDesigns.RevertDesign(_currentSave!);
//foreach (var actor in _currentData.Objects)
// _currentSave!.ApplyToActor(actor);
//
//if (_currentData.Objects.Count > 0)
// _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
//
//_currentSave!.Reset();
//if (_currentData.Objects.Count > 0)
// ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString());
//VisorBox();
var text = ImGui.GetClipboardText();
var design = _converter.FromBase64(text, true, true) ?? throw new Exception("The clipboard did not contain valid data.");
_stateManager.ApplyDesign(design, _state!, StateChanged.Source.Manual);
}
catch (Exception ex)
{
Glamourer.Chat.NotificationMessage(ex, $"Could not apply clipboard to {_identifier}.",
$"Could not apply clipboard to design {_identifier.Incognito(null)}", "Failure", NotificationType.Error);
}
}
//private unsafe void VisorBox()
//{
// var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch
// {
// ApplicationFlags.SetVisor => (0u, 3u),
// ApplicationFlags.Visor => (1u, 3u),
// ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u),
// _ => (2u, 3u),
// };
// var tmp = flags;
// if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask))
// {
// _currentSave.Data.Flags = flags switch
// {
// 0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor,
// 1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
// 2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
// _ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor),
// };
// if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor))
// {
// var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor);
// foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject))
// RedrawManager.SetVisor(actor.DrawObject.Pointer, on);
// }
// }
//}
private void CopyToClipboardButton()
{
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Copy.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
"Copy the current design to your clipboard.", false, true))
return;
try
{
var text = _converter.ShareBase64(_state!);
ImGui.SetClipboardText(text);
}
catch (Exception ex)
{
Glamourer.Chat.NotificationMessage(ex, $"Could not copy {_identifier} data to clipboard.",
$"Could not copy data from design {_identifier.Incognito(null)} to clipboard", "Failure", NotificationType.Error);
}
}
//private void DrawActorPanel()
//{
// using var group = ImRaii.Group();
// if (!_data.Identifier.IsValid)
// return;
//
// if (DrawCustomization(_currentSave.Customize, _currentSave.Equipment, !_data.Modifiable))
// //Glamourer.RedrawManager.Set(_data.Actor.Address, _character);
// Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true);
//
// if (ImGui.Button("Set Machinist Goggles"))
// Glamourer.RedrawManager.ChangeEquip(_data.Actor, EquipSlot.Head, new CharacterArmor(265, 1, 0));
//
// if (ImGui.Button("Set Weapon"))
// Glamourer.RedrawManager.LoadWeapon(_data.Actor.Address, new CharacterWeapon(0x00C9, 0x004E, 0x0001, 0x00),
// new CharacterWeapon(0x0065, 0x003D, 0x0001, 0x00));
//
// if (ImGui.Button("Set Customize"))
// {
// unsafe
// {
// var data = _data.Actor.Customize.Data->Clone();
// Glamourer.RedrawManager.UpdateCustomize(_data.Actor.DrawObject, new Customize(&data)
// {
// SkinColor = 154,
// });
// }
// }
//}
//
//private void DrawMonsterPanel()
//{
// using var group = ImRaii.Group();
// var currentModel = (uint)_data.Actor.ModelId;
// var models = GameData.Models(Dalamud.GameData);
// var currentData = models.Models.TryGetValue(currentModel, out var c) ? c.FirstName : $"#{currentModel}";
// using var combo = ImRaii.Combo("Model Id", currentData);
// if (!combo)
// return;
//
// foreach (var (id, data) in models.Models)
// {
// if (ImGui.Selectable(data.FirstName, id == currentModel) && id != currentModel)
// {
// _data.Actor.SetModelId((int)id);
// Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true);
// }
//
// ImGuiUtil.HoverTooltip(data.AllNames);
// }
//}
private void RevertButtons()
{
if (ImGui.Button("Revert to Game"))
_stateManager.ResetState(_state!);
ImGui.SameLine();
if (ImGui.Button("Reapply State"))
_stateManager.ReapplyState(_actor);
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Reapply Automation", Vector2.Zero, string.Empty, !_config.EnableAutoDesigns))
{
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!);
_stateManager.ReapplyState(_actor);
}
}
private void DrawApplyToSelf()
{
var (id, data) = _objects.PlayerData;
if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, "Apply the current state to your own character.",
!data.Valid || id == _identifier))
return;
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), state,
StateChanged.Source.Manual);
}
private void DrawApplyToTarget()
{
var (id, data) = _objects.TargetData;
var tt = id.IsValid
? data.Valid
? "Apply the current state to your current target."
: "The current target can not be manipulated."
: "No valid target selected.";
if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid || id == _identifier))
return;
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), state,
StateChanged.Source.Manual);
}
}

View file

@ -253,7 +253,7 @@ public unsafe class DebugTab : ITab
using var id = ImRaii.PushId("Visor");
ImGuiUtil.DrawTableColumn("Visor State");
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->DrawData.IsVisorToggled.ToString() : "No Character");
ImGuiUtil.DrawTableColumn(model.IsHuman ? _visorService.GetVisorState(model).ToString() : "No Human");
ImGuiUtil.DrawTableColumn(model.IsHuman ? VisorService.GetVisorState(model).ToString() : "No Human");
ImGui.TableNextColumn();
if (!model.IsHuman)
return;
@ -265,7 +265,7 @@ public unsafe class DebugTab : ITab
_visorService.SetVisorState(model, false);
ImGui.SameLine();
if (ImGui.SmallButton("Toggle"))
_visorService.SetVisorState(model, !_visorService.GetVisorState(model));
_visorService.SetVisorState(model, !VisorService.GetVisorState(model));
}
private void DrawHatState(Actor actor, Model model)
@ -1043,7 +1043,7 @@ public unsafe class DebugTab : ITab
PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]);
ImGui.TableNextRow();
if (state.BaseData.ModelId == 0 && state.ModelData.ModelId == 0)
if (state.BaseData.IsHuman && state.ModelData.IsHuman)
{
PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]);
ImGui.TableNextRow();
@ -1079,7 +1079,7 @@ public unsafe class DebugTab : ITab
public static void DrawDesignData(in DesignData data)
{
if (data.ModelId == 0)
if (data.IsHuman)
{
using var table = ImRaii.Table("##equip", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
if (!table)

View file

@ -7,6 +7,7 @@ using Dalamud.Interface.Internal.Notifications;
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
@ -176,7 +177,7 @@ public class DesignPanel
{
var set = _customizationService.AwaitedService.GetList(_selector.Selected!.DesignData.Customize.Clan,
_selector.Selected!.DesignData.Customize.Gender);
var all = CustomizationExtensions.All.Where(set.IsAvailable).Select(c => c.ToFlag()).Aggregate((a, b) => a | b);
var all = CustomizationExtensions.All.Where(set.IsAvailable).Select(c => c.ToFlag()).Aggregate((a, b) => a | b) | CustomizeFlag.Clan | CustomizeFlag.Gender;
var flags = (_selector.Selected!.ApplyCustomize & all) == 0 ? 0 : (_selector.Selected!.ApplyCustomize & all) == all ? 3 : 1;
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
{
@ -342,7 +343,7 @@ public class DesignPanel
return;
if (_state.GetOrCreate(id, data.Objects[0], out var state))
_state.ApplyDesign(_selector.Selected!, state);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
}
private void DrawApplyToTarget()
@ -357,6 +358,6 @@ public class DesignPanel
return;
if (_state.GetOrCreate(id, data.Objects[0], out var state))
_state.ApplyDesign(_selector.Selected!, state);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
}
}

View file

@ -23,7 +23,7 @@ public class VisorService : IDisposable
=> _setupVisorHook.Dispose();
/// <summary> Obtain the current state of the Visor for the given draw object (true: toggled). </summary>
public unsafe bool GetVisorState(Model characterBase)
public static unsafe bool GetVisorState(Model characterBase)
=> characterBase.IsCharacterBase && characterBase.AsCharacterBase->VisorToggled;
/// <summary> Manually set the state of the Visor for the given draw object. </summary>

View file

@ -5,31 +5,36 @@ using System.Runtime.CompilerServices;
using Dalamud.Data;
using Dalamud.Plugin;
using Glamourer.Customization;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
namespace Glamourer.Services;
public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationManager>
{
public CustomizationService(DalamudPluginInterface pi, DataManager gameData)
: base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData))
{ }
public readonly HumanModelList HumanModels;
public (Customize NewValue, CustomizeFlag Applied) Combine(Customize oldValues, Customize newValues, CustomizeFlag applyWhich)
public CustomizationService(DalamudPluginInterface pi, DataManager gameData, HumanModelList humanModels)
: base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData))
=> HumanModels = humanModels;
public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues,
CustomizeFlag applyWhich)
{
CustomizeFlag applied = 0;
CustomizeFlag changed = 0;
Customize ret = default;
ret.Load(oldValues);
if (applyWhich.HasFlag(CustomizeFlag.Clan))
{
ChangeClan(ref ret, newValues.Clan);
changed |= ChangeClan(ref ret, newValues.Clan);
applied |= CustomizeFlag.Clan;
}
if (applyWhich.HasFlag(CustomizeFlag.Gender))
if (ret.Race is not Race.Hrothgar || newValues.Gender is not Gender.Female)
{
ChangeGender(ref ret, newValues.Gender);
changed |= ChangeGender(ref ret, newValues.Gender);
applied |= CustomizeFlag.Gender;
}
@ -43,12 +48,14 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
var value = newValues[index];
if (IsCustomizationValid(set, ret.Face, index, value))
{
if (ret[index].Value != value.Value)
changed |= flag;
ret[index] = value;
applied |= flag;
}
}
return (ret, applied);
return (ret, applied, changed);
}
/// <summary> In languages other than english the actual clan name may depend on gender. </summary>
@ -190,10 +197,18 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
/// The returned model id is 0.
/// The return value is an empty string if everything was correct and a warning otherwise.
/// </summary>
public string ValidateModelId(uint modelId, out uint actualModelId)
public string ValidateModelId(uint modelId, out uint actualModelId, out bool isHuman)
{
if (modelId >= HumanModels.Count)
{
actualModelId = 0;
return modelId != 0 ? $"Model IDs different from 0 are not currently allowed, reset {modelId} to 0." : string.Empty;
isHuman = true;
return $"Model ID {modelId} is not an existing model, reset to 0.";
}
actualModelId = modelId;
isHuman = HumanModels.IsHuman(modelId);
return string.Empty;
}
/// <summary>

View file

@ -100,6 +100,7 @@ public static class ServiceManager
private static IServiceCollection AddState(this IServiceCollection services)
=> services.AddSingleton<StateManager>()
.AddSingleton<StateApplier>()
.AddSingleton<StateEditor>()
.AddSingleton<StateListener>()
.AddSingleton<FunModule>();

View file

@ -1,5 +1,4 @@
using System;
using Glamourer.Customization;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Structs;
@ -32,6 +31,37 @@ public class ActorState
/// <summary> The last seen job. </summary>
public byte LastJob;
/// <summary> The Lock-Key locking this state. </summary>
public uint Combination;
/// <summary> Whether the State is locked at all. </summary>
public bool IsLocked
=> Combination != 0;
/// <summary> Whether the given key can open the lock. </summary>
public bool CanUnlock(uint key)
=> !IsLocked || Combination == key;
/// <summary> Lock the current state for further manipulations. </summary>
public bool Lock(uint combination)
{
if (combination == 0)
return false;
if (Combination != 0)
return Combination == combination;
Combination = combination;
return true;
}
/// <summary> Unlock the current state. </summary>
public bool Unlock(uint key)
{
if (key == Combination)
Combination = 0;
return !IsLocked;
}
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
private readonly StateChanged.Source[] _sources = Enumerable
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray();

View file

@ -0,0 +1,275 @@
using System.Linq;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
/// <summary>
/// This class applies changes made to state to actual objects in the game.
/// It handles applying those changes as well as redrawing the actor if necessary.
/// </summary>
public class StateApplier
{
private readonly PenumbraService _penumbra;
private readonly UpdateSlotService _updateSlot;
private readonly VisorService _visor;
private readonly WeaponService _weapon;
private readonly MetaService _metaService;
private readonly ChangeCustomizeService _changeCustomize;
private readonly ItemManager _items;
private readonly ObjectManager _objects;
public StateApplier(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize,
ItemManager items, PenumbraService penumbra, MetaService metaService, ObjectManager objects)
{
_updateSlot = updateSlot;
_visor = visor;
_weapon = weapon;
_changeCustomize = changeCustomize;
_items = items;
_penumbra = penumbra;
_metaService = metaService;
_objects = objects;
}
/// <summary> Simply force a redraw regardless of conditions. </summary>
public void ForceRedraw(ActorData data)
{
foreach (var actor in data.Objects)
_penumbra.RedrawObject(actor, RedrawType.Redraw);
}
/// <inheritdoc cref="ForceRedraw(ActorData)"/>
public ActorData ForceRedraw(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ForceRedraw(data);
return data;
}
/// <summary>
/// Change the customization values of actors either by applying them via update or redrawing,
/// this depends on whether the changes include changes to Race, Gender, Body Type or Face.
/// </summary>
public void ChangeCustomize(ActorData data, in Customize customize)
{
foreach (var actor in data.Objects)
{
var mdl = actor.Model;
if (!mdl.IsCharacterBase)
continue;
var flags = Customize.Compare(mdl.GetCustomize(), customize);
if (!flags.RequiresRedraw() || !mdl.IsHuman)
_changeCustomize.UpdateCustomize(mdl, customize.Data);
else
_penumbra.RedrawObject(actor, RedrawType.Redraw);
}
}
/// <inheritdoc cref="ChangeCustomize(ActorData, in Customize)"/>
public ActorData ChangeCustomize(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeCustomize(data, state.ModelData.Customize);
return data;
}
/// <summary>
/// Change a single piece of armor and/or stain depending on slot.
/// This uses the current customization of the model to potentially prevent restricted gear types from appearing.
/// This never requires redrawing.
/// </summary>
public void ChangeArmor(ActorData data, EquipSlot slot, CharacterArmor armor, bool isHatVisible = true)
{
if (slot is EquipSlot.Head && !isHatVisible)
return;
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
{
var mdl = actor.Model;
if (!mdl.IsHuman)
continue;
var customize = mdl.GetCustomize();
var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender);
_updateSlot.UpdateSlot(actor.Model, slot, resolvedItem);
}
}
/// <inheritdoc cref="ChangeArmor(ActorData,EquipSlot,CharacterArmor,bool)"/>
public ActorData ChangeArmor(ActorState state, EquipSlot slot, bool apply)
{
var data = GetData(state);
if (apply)
ChangeArmor(data, slot, state.ModelData.Armor(slot), state.ModelData.IsHatVisible());
return data;
}
/// <summary>
/// Change the stain of a single piece of armor or weapon.
/// If the offhand is empty, the stain will be fixed to 0 to prevent crashes.
/// </summary>
public void ChangeStain(ActorData data, EquipSlot slot, StainId stain)
{
var idx = slot.ToIndex();
switch (idx)
{
case < 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_updateSlot.UpdateStain(actor.Model, slot, stain);
break;
case 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.MainHand, stain);
break;
case 11:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadStain(actor, EquipSlot.OffHand, stain);
break;
}
}
/// <inheritdoc cref="ChangeStain(ActorData,EquipSlot,StainId)"/>
public ActorData ChangeStain(ActorState state, EquipSlot slot, bool apply)
{
var data = GetData(state);
if (apply)
ChangeStain(data, slot, state.ModelData.Stain(slot));
return data;
}
/// <summary> Apply a weapon to the appropriate slot. </summary>
public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainId stain)
{
if (slot is EquipSlot.MainHand)
ChangeMainhand(data, item, stain);
else
ChangeOffhand(data, item, stain);
}
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/>
public ActorData ChangeWeapon(ActorState state, EquipSlot slot, bool apply)
{
var data = GetData(state);
if (apply)
ChangeWeapon(data, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
return data;
}
/// <summary>
/// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both.
/// </summary>
public void ChangeMainhand(ActorData data, EquipItem weapon, StainId stain)
{
var slot = weapon.Type.Offhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stain));
}
/// <inheritdoc cref="ChangeMainhand(ActorData,EquipItem,StainId)"/>
public ActorData ChangeMainhand(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeMainhand(data, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
return data;
}
/// <summary> Apply a weapon to the offhand. </summary>
public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain)
{
stain = weapon.ModelId.Value == 0 ? 0 : stain;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain));
}
/// <inheritdoc cref="ChangeOffhand(ActorData,EquipItem,StainId)"/>
public ActorData ChangeOffhand(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeOffhand(data, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
return data;
}
/// <summary> Change the visor state of actors only on the draw object. </summary>
public void ChangeVisor(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_visor.SetVisorState(actor.Model, value);
}
/// <inheritdoc cref="ChangeVisor(ActorData, bool)"/>
public ActorData ChangeVisor(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeVisor(data, state.ModelData.IsVisorToggled());
return data;
}
/// <summary> Change the forced wetness state on actors. </summary>
public unsafe void ChangeWetness(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
actor.AsCharacter->IsGPoseWet = value;
}
/// <inheritdoc cref="ChangeWetness(ActorData, bool)"/>
public ActorData ChangeWetness(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeWetness(data, state.ModelData.IsWet());
return data;
}
/// <summary> Change the hat-visibility state on actors. </summary>
public void ChangeHatState(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetHatState(actor, value);
}
/// <inheritdoc cref="ChangeHatState(ActorData, bool)"/>
public ActorData ChangeHatState(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeHatState(data, state.ModelData.IsHatVisible());
return data;
}
/// <summary> Change the weapon-visibility state on actors. </summary>
public void ChangeWeaponState(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetWeaponState(actor, value);
}
/// <inheritdoc cref="ChangeWeaponState(ActorData, bool)"/>
public ActorData ChangeWeaponState(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeWeaponState(data, state.ModelData.IsWeaponVisible());
return data;
}
private ActorData GetData(ActorState state)
{
_objects.Update();
return _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid;
}
}

View file

@ -1,166 +1,174 @@
using System.Linq;
using System;
using System.Linq;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Events;
using Glamourer.Services;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
/// <summary>
/// This class applies changes made to state to actual objects in the game.
/// It handles applying those changes as well as redrawing the actor if necessary.
/// </summary>
public class StateEditor
{
private readonly PenumbraService _penumbra;
private readonly UpdateSlotService _updateSlot;
private readonly VisorService _visor;
private readonly WeaponService _weapon;
private readonly MetaService _metaService;
private readonly ChangeCustomizeService _changeCustomize;
private readonly ItemManager _items;
private readonly CustomizationService _customizations;
private readonly HumanModelList _humans;
public StateEditor(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize,
ItemManager items, PenumbraService penumbra, MetaService metaService)
public StateEditor(CustomizationService customizations, HumanModelList humans, ItemManager items)
{
_updateSlot = updateSlot;
_visor = visor;
_weapon = weapon;
_changeCustomize = changeCustomize;
_customizations = customizations;
_humans = humans;
_items = items;
_penumbra = penumbra;
_metaService = metaService;
}
/// <summary> Changing the model ID simply requires guaranteed redrawing. </summary>
public void ChangeModelId(ActorData data, uint modelId)
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source,
out uint oldModelId, uint key = 0)
{
foreach (var actor in data.Objects)
_penumbra.RedrawObject(actor, RedrawType.Redraw);
oldModelId = state.ModelData.ModelId;
if (!state.CanUnlock(key))
return false;
var oldIsHuman = state.ModelData.IsHuman;
state.ModelData.IsHuman = _humans.IsHuman(modelId);
if (state.ModelData.IsHuman)
{
if (oldModelId == modelId)
return true;
state.ModelData.ModelId = modelId;
if (oldIsHuman)
return true;
// Fix up everything else to make sure the result is a valid human.
state.ModelData.Customize = Customize.Default;
state.ModelData.SetDefaultEquipment(_items);
state.ModelData.SetHatVisible(true);
state.ModelData.SetWeaponVisible(true);
state.ModelData.SetVisor(false);
state[ActorState.MetaIndex.ModelId] = source;
state[ActorState.MetaIndex.HatState] = source;
state[ActorState.MetaIndex.WeaponState] = source;
state[ActorState.MetaIndex.VisorState] = source;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state[slot, true] = source;
state[slot, false] = source;
}
/// <summary>
/// Change the customization values of actors either by applying them via update or redrawing,
/// this depends on whether the changes include changes to Race, Gender, Body Type or Face.
/// </summary>
public void ChangeCustomize(ActorData data, Customize customize)
{
foreach (var actor in data.Objects)
{
var mdl = actor.Model;
if (!mdl.IsHuman)
continue;
var flags = Customize.Compare(mdl.GetCustomize(), customize);
if (!flags.RequiresRedraw())
_changeCustomize.UpdateCustomize(mdl, customize.Data);
state[CustomizeIndex.Clan] = source;
state[CustomizeIndex.Gender] = source;
var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
state[index] = source;
}
else
_penumbra.RedrawObject(actor, RedrawType.Redraw);
{
unsafe
{
state.ModelData.LoadNonHuman(modelId, customize, (byte*)equipData);
state[ActorState.MetaIndex.ModelId] = source;
}
}
/// <summary>
/// Change a single piece of armor and/or stain depending on slot.
/// This uses the current customization of the model to potentially prevent restricted gear types from appearing.
/// This never requires redrawing.
/// </summary>
public void ChangeArmor(ActorData data, EquipSlot slot, CharacterArmor armor)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
{
var mdl = actor.Model;
var customize = mdl.IsHuman ? mdl.GetCustomize() : actor.GetCustomize();
var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender);
_updateSlot.UpdateSlot(actor.Model, slot, resolvedItem);
}
return true;
}
/// <summary>
/// Change the stain of a single piece of armor or weapon.
/// If the offhand is empty, the stain will be fixed to 0 to prevent crashes.
/// </summary>
public void ChangeStain(ActorData data, EquipSlot slot, StainId stain)
/// <summary> Change a customization value. </summary>
public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source,
out CustomizeValue old, uint key = 0)
{
var idx = slot.ToIndex();
switch (idx)
{
case < 10:
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_updateSlot.UpdateStain(actor.Model, slot, stain);
break;
case 10:
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_weapon.LoadStain(actor, EquipSlot.MainHand, stain);
break;
case 11:
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_weapon.LoadStain(actor, EquipSlot.OffHand, stain);
break;
}
old = state.ModelData.Customize[idx];
if (!state.CanUnlock(key))
return false;
state.ModelData.Customize[idx] = value;
state[idx] = source;
return true;
}
/// <summary> Apply a weapon to the appropriate slot. </summary>
public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainId stain)
/// <summary> Change an entire customization array according to flags. </summary>
public bool ChangeHumanCustomize(ActorState state, in Customize customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
out Customize old, out CustomizeFlag changed, uint key = 0)
{
if (slot is EquipSlot.MainHand)
ChangeMainhand(data, item, stain);
else
ChangeOffhand(data, item, stain);
old = state.ModelData.Customize;
changed = 0;
if (!state.CanUnlock(key))
return false;
(var customize, var applied, changed) = _customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich);
if (changed == 0)
return false;
state.ModelData.Customize = customize;
applied |= changed;
foreach (var type in Enum.GetValues<CustomizeIndex>())
{
if (applied.HasFlag(type.ToFlag()))
state[type] = source;
}
/// <summary>
/// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both.
/// </summary>
public void ChangeMainhand(ActorData data, EquipItem weapon, StainId stain)
{
var slot = weapon.Type.Offhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand;
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stain));
return true;
}
/// <summary> Apply a weapon to the offhand. </summary>
public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain)
/// <summary> Change a single piece of equipment without stain. </summary>
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, out EquipItem oldItem, uint key = 0)
{
stain = weapon.ModelId.Value == 0 ? 0 : stain;
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain));
oldItem = state.ModelData.Item(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetItem(slot, item);
state[slot, false] = source;
return true;
}
/// <summary> Change the visor state of actors only on the draw object. </summary>
public void ChangeVisor(ActorData data, bool value)
/// <summary> Change a single piece of equipment including stain. </summary>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, out EquipItem oldItem,
out StainId oldStain, uint key = 0)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
{
var mdl = actor.Model;
if (!mdl.IsHuman)
continue;
oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
_visor.SetVisorState(mdl, value);
}
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
state[slot, false] = source;
state[slot, true] = source;
return true;
}
/// <summary> Change the forced wetness state on actors. </summary>
public unsafe void ChangeWetness(ActorData data, bool value)
/// <summary> Change only the stain of an equipment piece. </summary>
public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, out StainId oldStain, uint key = 0)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
actor.AsCharacter->IsGPoseWet = value;
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetStain(slot, stain);
state[slot, true] = source;
return true;
}
/// <summary> Change the hat-visibility state on actors. </summary>
public unsafe void ChangeHatState(ActorData data, bool value)
public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
uint key = 0)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetHatState(actor, value);
}
(var setter, oldValue) = index switch
{
ActorState.MetaIndex.Wetness => ((Func<bool, bool>)state.ModelData.SetIsWet, state.ModelData.IsWet()),
ActorState.MetaIndex.HatState => ((Func<bool, bool>)state.ModelData.SetHatVisible, state.ModelData.IsHatVisible()),
ActorState.MetaIndex.VisorState => ((Func<bool, bool>)state.ModelData.SetVisor, state.ModelData.IsVisorToggled()),
ActorState.MetaIndex.WeaponState => ((Func<bool, bool>)state.ModelData.SetWeaponVisible, state.ModelData.IsWeaponVisible()),
_ => throw new Exception("Invalid MetaIndex."),
};
/// <summary> Change the weapon-visibility state on actors. </summary>
public unsafe void ChangeWeaponState(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetWeaponState(actor, value);
if (!state.CanUnlock(key))
return false;
setter(value);
state[index] = source;
return true;
}
}

View file

@ -6,6 +6,7 @@ using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using OtterGui.Classes;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -30,6 +31,7 @@ public class StateListener : IDisposable
private readonly WeaponVisibilityChanged _weaponVisibility;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly FunModule _funModule;
private readonly HumanModelList _humans;
public bool Enabled
{
@ -39,7 +41,7 @@ public class StateListener : IDisposable
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule)
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans)
{
_manager = manager;
_items = items;
@ -53,6 +55,7 @@ public class StateListener : IDisposable
_headGearVisibility = headGearVisibility;
_autoDesignApplier = autoDesignApplier;
_funModule = funModule;
_humans = humans;
if (Enabled)
Subscribe();
@ -320,10 +323,11 @@ public class StateListener : IDisposable
// Model ID did change, reload entire state accordingly.
// Always use the actor for the base data.
if (modelId == 0)
state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, (byte*)equipData);
else
var isHuman = _humans.IsHuman(modelId);
if (isHuman)
state.BaseData = _manager.FromActor(actor, false);
else
state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, (byte*)equipData);
return UpdateState.Change;
}

View file

@ -10,6 +10,7 @@ using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -19,24 +20,22 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
{
private readonly ActorService _actors;
private readonly ItemManager _items;
private readonly CustomizationService _customizations;
private readonly VisorService _visor;
private readonly HumanModelList _humans;
private readonly StateChanged _event;
private readonly ObjectManager _objects;
private readonly StateApplier _applier;
private readonly StateEditor _editor;
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor, StateChanged @event,
ObjectManager objects, StateEditor editor)
public StateManager(ActorService actors, ItemManager items, StateChanged @event, StateApplier applier, StateEditor editor,
HumanModelList humans)
{
_actors = actors;
_items = items;
_customizations = customizations;
_visor = visor;
_event = @event;
_objects = objects;
_applier = applier;
_editor = editor;
_humans = humans;
}
public IEnumerator<KeyValuePair<ActorIdentifier, ActorState>> GetEnumerator()
@ -109,17 +108,18 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
return ret;
}
var model = actor.Model;
// Model ID is only unambiguously contained in the game object.
// The draw object only has the object type.
// TODO reverse search model data to get model id from model.
if (actor.AsCharacter->CharacterData.ModelCharaId != 0)
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
{
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData,
(byte*)&actor.AsCharacter->DrawData.Head);
return ret;
}
var model = actor.Model;
CharacterWeapon main;
CharacterWeapon off;
@ -152,7 +152,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
(_, _, main, off) = model.GetWeapons(actor);
// Visor state is a flag on the game object, but we can see the actual state on the draw object.
ret.SetVisor(_visor.GetVisorState(model));
ret.SetVisor(VisorService.GetVisorState(model));
}
else
{
@ -192,304 +192,228 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
#region Change Values
/// <summary> Turn a non-human actor human. </summary>
public void TurnHuman(ActorState state, StateChanged.Source source)
/// <summary> Turn an actor human. </summary>
public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0)
=> ChangeModelId(state, 0, Customize.Default, nint.Zero, source, key);
/// <summary> Turn an actor to. </summary>
public void ChangeModelId(ActorState state, uint modelId, Customize customize, nint equipData, StateChanged.Source source,
uint key = 0)
{
if (state.ModelData.ModelId == 0)
if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
return;
state.ModelData.ModelId = 0;
state[ActorState.MetaIndex.ModelId] = source;
ChangeCustomize(state, Customize.Default, CustomizeFlagExtensions.All, source);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
ChangeEquip(state, slot, ItemManager.NothingItem(slot), 0, source);
ChangeEquip(state, EquipSlot.MainHand, _items.DefaultSword, 0, source);
ChangeEquip(state, EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield), 0, source);
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
_editor.ChangeModelId(objects, 0);
var actors = _applier.ForceRedraw(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
}
/// <summary> Change a customization value. </summary>
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source)
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.Customize[idx];
state.ModelData.Customize[idx] = value;
state[idx] = source;
if (!_editor.ChangeCustomize(state, idx, value, source, out var old, key))
return;
// Update draw objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeCustomize(objects, state.ModelData.Customize);
// Meta.
var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier} from {old.Value} to {value.Value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Customize, source, state, objects, (old, value, idx));
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Customize, source, state, actors, (old, value, idx));
}
/// <summary> Change an entire customization array according to flags. </summary>
public void ChangeCustomize(ActorState state, in Customize customizeInput, CustomizeFlag apply, StateChanged.Source source)
public void ChangeCustomize(ActorState state, in Customize customizeInput, CustomizeFlag apply, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.Customize;
var (customize, applied) = _customizations.Combine(state.ModelData.Customize, customizeInput, apply);
if (applied == 0)
if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key))
return;
state.ModelData.Customize = customize;
foreach (var type in Enum.GetValues<CustomizeIndex>())
{
var flag = type.ToFlag();
if (applied.HasFlag(flag))
state[type] = source;
}
// Update draw objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeCustomize(objects, state.ModelData.Customize);
// Meta.
var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {applied} customizations in state {state.Identifier} from {old} to {customize}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Customize, source, state, objects, (old, customize, applied));
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied));
}
/// <summary> Change a single piece of equipment without stain. </summary>
/// <remarks> Do not use this in the same frame as ChangeStain, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source)"/> instead. </remarks>
public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source)
/// <remarks> Do not use this in the same frame as ChangeStain, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.Item(slot);
state.ModelData.SetItem(slot, item);
state[slot, false] = source;
var type = slot is EquipSlot.MainHand or EquipSlot.OffHand ? StateChanged.Type.Weapon : StateChanged.Type.Equip;
if (!_editor.ChangeItem(state, slot, item, source, out var old, key))
return;
// Update draw objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
if (type == StateChanged.Type.Equip)
{
if (slot is not EquipSlot.Head || state.ModelData.IsHatVisible())
_editor.ChangeArmor(objects, slot, state.ModelData.Armor(slot));
}
else
{
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
}
// Meta.
var type = slot.IsEquipmentPiece() ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, objects, (old, item, slot));
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot));
}
/// <summary> Change a single piece of equipment including stain. </summary>
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source)
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.Item(slot);
var oldStain = state.ModelData.Stain(slot);
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
state[slot, false] = source;
state[slot, true] = source;
var type = slot is EquipSlot.MainHand or EquipSlot.OffHand ? StateChanged.Type.Weapon : StateChanged.Type.Equip;
if (!_editor.ChangeEquip(state, slot, item, stain, source, out var old, out var oldStain, key))
return;
// Update draw objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
if (type == StateChanged.Type.Equip)
{
if (slot is not EquipSlot.Head || state.ModelData.IsHatVisible())
_editor.ChangeArmor(objects, slot, state.ModelData.Armor(slot));
}
else
{
_editor.ChangeWeapon(objects, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
}
// Meta.
var type = slot.IsEquipmentPiece() ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}) and its stain from {oldStain.Value} to {stain.Value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, objects, (old, item, slot));
_event.Invoke(StateChanged.Type.Stain, source, state, objects, (oldStain, stain, slot));
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}) and its stain from {oldStain.Value} to {stain.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot));
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot));
}
/// <summary> Change only the stain of an equipment piece. </summary>
/// <remarks>
/// Do not use this in the same frame as ChangeEquip, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source)"/> instead. </remarks>
public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source)
/// <remarks> Do not use this in the same frame as ChangeEquip, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.Stain(slot);
state.ModelData.SetStain(slot, stain);
state[slot, true] = source;
if (!_editor.ChangeStain(state, slot, stain, source, out var old, key))
return;
// Update draw objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeStain(objects, slot, stain);
// Meta.
var actors = _applier.ChangeStain(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier} from {old.Value} to {stain.Value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Stain, source, state, objects, (old, stain, slot));
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Value} to {stain.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
}
/// <summary> Change hat visibility. </summary>
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source)
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.IsHatVisible();
state.ModelData.SetHatVisible(value);
state[ActorState.MetaIndex.HatState] = source;
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, value, source, out var old, key))
return;
// Update draw objects / game objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeHatState(objects, value);
// Meta.
var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set Head Gear Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaIndex.HatState));
$"Set Head Gear Visibility in state {state.Identifier} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.HatState));
}
/// <summary> Change weapon visibility. </summary>
public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source)
public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.IsWeaponVisible();
state.ModelData.SetWeaponVisible(value);
state[ActorState.MetaIndex.WeaponState] = source;
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, value, source, out var old, key))
return;
// Update draw objects / game objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeWeaponState(objects, value);
// Meta.
var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set Weapon Visibility in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaIndex.WeaponState));
$"Set Weapon Visibility in state {state.Identifier} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.WeaponState));
}
/// <summary> Change visor state. </summary>
public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source)
public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.IsVisorToggled();
state.ModelData.SetVisor(value);
state[ActorState.MetaIndex.VisorState] = source;
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, value, source, out var old, key))
return;
// Update draw objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
if (source is StateChanged.Source.Manual)
_editor.ChangeVisor(objects, value);
// Meta.
var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set Visor State in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, objects, (old, value, ActorState.MetaIndex.VisorState));
$"Set Visor State in state {state.Identifier} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.VisorState));
}
/// <summary> Set GPose Wetness. </summary>
public void ChangeWetness(ActorState state, bool value, StateChanged.Source source)
public void ChangeWetness(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{
// Update state data.
var old = state.ModelData.IsWet();
state.ModelData.SetIsWet(value);
state[ActorState.MetaIndex.Wetness] = source;
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, value, source, out var old, key))
return;
// Update draw objects / game objects.
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
_editor.ChangeWetness(objects, value);
// Meta.
var actors = _applier.ChangeVisor(state, true);
Glamourer.Log.Verbose(
$"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaIndex.Wetness], state, objects, (old, value, ActorState.MetaIndex.Wetness));
$"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaIndex.Wetness], state, actors, (old, value, ActorState.MetaIndex.Wetness));
}
#endregion
public void ApplyDesign(DesignBase design, ActorState state)
public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0)
{
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
{
switch (applyPiece, applyStain)
var unused = (applyPiece, applyStain) switch
{
case (false, false): break;
case (true, false):
ChangeItem(state, slot, design.DesignData.Item(slot), StateChanged.Source.Manual);
break;
case (false, true):
ChangeStain(state, slot, design.DesignData.Stain(slot), StateChanged.Source.Manual);
break;
case (true, true):
ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), StateChanged.Source.Manual);
break;
}
(false, false) => false,
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
out _, key),
};
}
if (state.ModelData.ModelId != 0 && design.DesignData.ModelId == 0)
TurnHuman(state, StateChanged.Source.Manual);
if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.DesignData.GetEquipmentPtr(), source,
out var oldModelId, key))
return;
if (design.DoApplyHatVisible())
ChangeHatState(state, design.DesignData.IsHatVisible(), StateChanged.Source.Manual);
if (design.DoApplyWeaponVisible())
ChangeWeaponState(state, design.DesignData.IsWeaponVisible(), StateChanged.Source.Manual);
if (design.DoApplyVisorToggle())
ChangeVisorState(state, design.DesignData.IsVisorToggled(), StateChanged.Source.Manual);
var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman;
if (design.DoApplyWetness())
ChangeWetness(state, design.DesignData.IsWet(), StateChanged.Source.Manual);
_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key);
ChangeCustomize(state, design.DesignData.Customize, design.ApplyCustomize, StateChanged.Source.Manual);
if (state.ModelData.IsHuman)
{
if (design.DoApplyHatVisible())
_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key);
if (design.DoApplyWeaponVisible())
_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key);
if (design.DoApplyVisorToggle())
_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
_editor.ChangeHumanCustomize(state, design.DesignData.Customize, design.ApplyCustomize, source, out _, out var applied, key);
redraw |= applied.RequiresRedraw();
foreach (var slot in EquipSlotExtensions.FullSlots)
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
}
HandleEquip(EquipSlot.MainHand,
design.DoApplyEquip(EquipSlot.MainHand)
&& design.DesignData.Item(EquipSlot.MainHand).Type == state.BaseData.Item(EquipSlot.MainHand).Type,
design.DoApplyStain(EquipSlot.MainHand));
HandleEquip(EquipSlot.OffHand,
design.DoApplyEquip(EquipSlot.OffHand)
&& design.DesignData.Item(EquipSlot.OffHand).Type == state.BaseData.Item(EquipSlot.OffHand).Type,
design.DoApplyStain(EquipSlot.OffHand));
var actors = ApplyAll(state, redraw);
Glamourer.Log.Verbose(
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design);
}
private ActorData ApplyAll(ActorState state, bool redraw)
{
var actors = _applier.ChangeWetness(state, true);
if (redraw)
{
_applier.ForceRedraw(actors);
}
else
{
_applier.ChangeCustomize(actors, state.ModelData.Customize);
_applier.ChangeHatState(actors, state.ModelData.IsHatVisible());
_applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible());
_applier.ChangeVisor(actors, state.ModelData.IsVisorToggled());
foreach (var slot in EquipSlotExtensions.EqdpSlots)
_applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.ModelData.IsHatVisible());
foreach (var slot in EquipSlotExtensions.WeaponSlots)
_applier.ChangeWeapon(actors, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot));
}
return actors;
}
public void ResetState(ActorState state)
{
ChangeHatState(state, state.BaseData.IsHatVisible(), StateChanged.Source.Game);
ChangeVisorState(state, state.BaseData.IsVisorToggled(), StateChanged.Source.Game);
ChangeWeaponState(state, state.BaseData.IsWeaponVisible(), StateChanged.Source.Game);
ChangeWetness(state, false, StateChanged.Source.Game);
ChangeCustomize(state, state.BaseData.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Game);
var redraw = state.ModelData.ModelId != state.BaseData.ModelId || !state.ModelData.IsHuman
|| Customize.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
state.ModelData = state.BaseData;
foreach (var index in Enum.GetValues<CustomizeIndex>())
state[index] = StateChanged.Source.Game;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state[slot, true] = StateChanged.Source.Game;
state[slot, false] = StateChanged.Source.Game;
}
foreach (var slot in EquipSlotExtensions.EqdpSlots)
ChangeEquip(state, slot, state.BaseData.Item(slot), state.BaseData.Stain(slot), StateChanged.Source.Game);
foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
state[type] = StateChanged.Source.Game;
ChangeEquip(state, EquipSlot.MainHand, state.BaseData.Item(EquipSlot.MainHand), state.BaseData.Stain(EquipSlot.MainHand),
StateChanged.Source.Game);
ChangeEquip(state, EquipSlot.OffHand, state.BaseData.Item(EquipSlot.OffHand), state.BaseData.Stain(EquipSlot.OffHand),
StateChanged.Source.Game);
_objects.Update();
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
foreach (var actor in objects.Objects)
ReapplyState(actor);
var actors = ApplyAll(state, redraw);
Glamourer.Log.Verbose(
$"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, null);
}
public void ReapplyState(Actor actor)
@ -497,28 +421,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
if (!GetOrCreate(actor, out var state))
return;
var mdl = actor.Model;
if (!mdl.IsHuman)
return;
var data = new ActorData(actor, string.Empty);
var customizeFlags = Customize.Compare(mdl.GetCustomize(), state.ModelData.Customize);
_editor.ChangeHatState(data, state.ModelData.IsHatVisible());
_editor.ChangeWetness(data, false);
_editor.ChangeWeaponState(data, state.ModelData.IsWeaponVisible());
_editor.ChangeVisor(data, state.ModelData.IsVisorToggled());
_editor.ChangeCustomize(data, state.ModelData.Customize);
if (customizeFlags.RequiresRedraw())
return;
if (state.ModelData.IsHatVisible())
_editor.ChangeArmor(data, EquipSlot.Head, state.ModelData.Armor(EquipSlot.Head));
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
_editor.ChangeArmor(data, slot, state.ModelData.Armor(slot));
_editor.ChangeMainhand(data, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
_editor.ChangeOffhand(data, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
ApplyAll(state, !actor.Model.IsHuman || Customize.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw());
}
public void DeleteState(ActorIdentifier identifier)