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.Game.ClientState.Objects.Types;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Penumbra.Api.Helpers; using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
@ -76,7 +77,7 @@ public partial class GlamourerIpc
continue; 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); _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) public void Reduce(Actor actor, ActorIdentifier identifier, ActorState state)
{ {
if (!_config.EnableAutoDesigns) if (!_config.EnableAutoDesigns)

View file

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

View file

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

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Buffers.Text;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Services; using Glamourer.Services;
@ -33,6 +34,7 @@ public unsafe struct DesignData
private FullEquipType _typeMainhand; private FullEquipType _typeMainhand;
private FullEquipType _typeOffhand; private FullEquipType _typeOffhand;
private byte _states; private byte _states;
public bool IsHuman = true;
public DesignData() public DesignData()
{ } { }
@ -202,14 +204,49 @@ public unsafe struct DesignData
SetStain(EquipSlot.OffHand, 0); SetStain(EquipSlot.OffHand, 0);
} }
public void LoadNonHuman(uint modelId, Customize customize, byte* equipData)
public bool LoadNonHuman(uint modelId, Customize customize, byte* equipData)
{ {
ModelId = modelId; ModelId = modelId;
IsHuman = false;
Customize.Load(customize); Customize.Load(customize);
fixed (byte* ptr = _equipmentBytes) fixed (byte* ptr = _equipmentBytes)
{ {
MemoryUtility.MemCpyUnchecked(ptr, equipData, 40); 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() public readonly byte[] GetCustomizeBytes()
@ -234,6 +271,32 @@ public unsafe struct DesignData
return ret; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T> 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 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> /// <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, 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> /// <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, 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> /// <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, Other,
} }

View file

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

View file

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

View file

@ -7,6 +7,7 @@ using Dalamud.Interface.Internal.Notifications;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment; using Glamourer.Gui.Equipment;
using Glamourer.Interop; using Glamourer.Interop;
@ -176,7 +177,7 @@ public class DesignPanel
{ {
var set = _customizationService.AwaitedService.GetList(_selector.Selected!.DesignData.Customize.Clan, var set = _customizationService.AwaitedService.GetList(_selector.Selected!.DesignData.Customize.Clan,
_selector.Selected!.DesignData.Customize.Gender); _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; var flags = (_selector.Selected!.ApplyCustomize & all) == 0 ? 0 : (_selector.Selected!.ApplyCustomize & all) == all ? 3 : 1;
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
{ {
@ -342,7 +343,7 @@ public class DesignPanel
return; return;
if (_state.GetOrCreate(id, data.Objects[0], out var state)) 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() private void DrawApplyToTarget()
@ -357,6 +358,6 @@ public class DesignPanel
return; return;
if (_state.GetOrCreate(id, data.Objects[0], out var state)) 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(); => _setupVisorHook.Dispose();
/// <summary> Obtain the current state of the Visor for the given draw object (true: toggled). </summary> /// <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; => characterBase.IsCharacterBase && characterBase.AsCharacterBase->VisorToggled;
/// <summary> Manually set the state of the Visor for the given draw object. </summary> /// <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.Data;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Customization; using Glamourer.Customization;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
namespace Glamourer.Services; namespace Glamourer.Services;
public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationManager> public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationManager>
{ {
public CustomizationService(DalamudPluginInterface pi, DataManager gameData) public readonly HumanModelList HumanModels;
: base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData))
{ }
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 applied = 0;
CustomizeFlag changed = 0;
Customize ret = default; Customize ret = default;
ret.Load(oldValues); ret.Load(oldValues);
if (applyWhich.HasFlag(CustomizeFlag.Clan)) if (applyWhich.HasFlag(CustomizeFlag.Clan))
{ {
ChangeClan(ref ret, newValues.Clan); changed |= ChangeClan(ref ret, newValues.Clan);
applied |= CustomizeFlag.Clan; applied |= CustomizeFlag.Clan;
} }
if (applyWhich.HasFlag(CustomizeFlag.Gender)) if (applyWhich.HasFlag(CustomizeFlag.Gender))
if (ret.Race is not Race.Hrothgar || newValues.Gender is not Gender.Female) 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; applied |= CustomizeFlag.Gender;
} }
@ -43,12 +48,14 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
var value = newValues[index]; var value = newValues[index];
if (IsCustomizationValid(set, ret.Face, index, value)) if (IsCustomizationValid(set, ret.Face, index, value))
{ {
if (ret[index].Value != value.Value)
changed |= flag;
ret[index] = value; ret[index] = value;
applied |= flag; applied |= flag;
} }
} }
return (ret, applied); return (ret, applied, changed);
} }
/// <summary> In languages other than english the actual clan name may depend on gender. </summary> /// <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 returned model id is 0.
/// The return value is an empty string if everything was correct and a warning otherwise. /// The return value is an empty string if everything was correct and a warning otherwise.
/// </summary> /// </summary>
public string ValidateModelId(uint modelId, out uint actualModelId) public string ValidateModelId(uint modelId, out uint actualModelId, out bool isHuman)
{ {
actualModelId = 0; if (modelId >= HumanModels.Count)
return modelId != 0 ? $"Model IDs different from 0 are not currently allowed, reset {modelId} to 0." : string.Empty; {
actualModelId = 0;
isHuman = true;
return $"Model ID {modelId} is not an existing model, reset to 0.";
}
actualModelId = modelId;
isHuman = HumanModels.IsHuman(modelId);
return string.Empty;
} }
/// <summary> /// <summary>

View file

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

View file

@ -1,5 +1,4 @@
using System; using Glamourer.Customization;
using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Structs; using Glamourer.Structs;
@ -32,6 +31,37 @@ public class ActorState
/// <summary> The last seen job. </summary> /// <summary> The last seen job. </summary>
public byte LastJob; 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> /// <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 private readonly StateChanged.Source[] _sources = Enumerable
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray(); .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.Customization;
using Glamourer.Interop; using Glamourer.Events;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using Penumbra.Api.Enums; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
namespace Glamourer.State; 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 public class StateEditor
{ {
private readonly PenumbraService _penumbra; private readonly ItemManager _items;
private readonly UpdateSlotService _updateSlot; private readonly CustomizationService _customizations;
private readonly VisorService _visor; private readonly HumanModelList _humans;
private readonly WeaponService _weapon;
private readonly MetaService _metaService;
private readonly ChangeCustomizeService _changeCustomize;
private readonly ItemManager _items;
public StateEditor(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize, public StateEditor(CustomizationService customizations, HumanModelList humans, ItemManager items)
ItemManager items, PenumbraService penumbra, MetaService metaService)
{ {
_updateSlot = updateSlot; _customizations = customizations;
_visor = visor; _humans = humans;
_weapon = weapon; _items = items;
_changeCustomize = changeCustomize;
_items = items;
_penumbra = penumbra;
_metaService = metaService;
} }
/// <summary> Changing the model ID simply requires guaranteed redrawing. </summary> /// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
public void ChangeModelId(ActorData data, uint modelId) 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) oldModelId = state.ModelData.ModelId;
_penumbra.RedrawObject(actor, RedrawType.Redraw); if (!state.CanUnlock(key))
} return false;
/// <summary> var oldIsHuman = state.ModelData.IsHuman;
/// Change the customization values of actors either by applying them via update or redrawing, state.ModelData.IsHuman = _humans.IsHuman(modelId);
/// this depends on whether the changes include changes to Race, Gender, Body Type or Face. if (state.ModelData.IsHuman)
/// </summary>
public void ChangeCustomize(ActorData data, Customize customize)
{
foreach (var actor in data.Objects)
{ {
var mdl = actor.Model; if (oldModelId == modelId)
if (!mdl.IsHuman) return true;
continue;
var flags = Customize.Compare(mdl.GetCustomize(), customize); state.ModelData.ModelId = modelId;
if (!flags.RequiresRedraw()) if (oldIsHuman)
_changeCustomize.UpdateCustomize(mdl, customize.Data); return true;
else
_penumbra.RedrawObject(actor, RedrawType.Redraw); // 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;
}
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;
} }
}
/// <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);
}
}
/// <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.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;
}
}
/// <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 else
ChangeOffhand(data, item, stain);
}
/// <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));
}
/// <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.IsCharacter))
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain));
}
/// <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.IsCharacter))
{ {
var mdl = actor.Model; unsafe
if (!mdl.IsHuman) {
continue; state.ModelData.LoadNonHuman(modelId, customize, (byte*)equipData);
state[ActorState.MetaIndex.ModelId] = source;
_visor.SetVisorState(mdl, value); }
} }
return true;
} }
/// <summary> Change the forced wetness state on actors. </summary> /// <summary> Change a customization value. </summary>
public unsafe void ChangeWetness(ActorData data, bool value) public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source,
out CustomizeValue old, uint key = 0)
{ {
foreach (var actor in data.Objects.Where(a => a.IsCharacter)) old = state.ModelData.Customize[idx];
actor.AsCharacter->IsGPoseWet = value; if (!state.CanUnlock(key))
return false;
state.ModelData.Customize[idx] = value;
state[idx] = source;
return true;
} }
/// <summary> Change the hat-visibility state on actors. </summary> /// <summary> Change an entire customization array according to flags. </summary>
public unsafe void ChangeHatState(ActorData data, bool value) public bool ChangeHumanCustomize(ActorState state, in Customize customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
out Customize old, out CustomizeFlag changed, uint key = 0)
{ {
foreach (var actor in data.Objects.Where(a => a.IsCharacter)) old = state.ModelData.Customize;
_metaService.SetHatState(actor, value); 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;
}
return true;
} }
/// <summary> Change the weapon-visibility state on actors. </summary> /// <summary> Change a single piece of equipment without stain. </summary>
public unsafe void ChangeWeaponState(ActorData data, bool value) public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, out EquipItem oldItem, uint key = 0)
{ {
foreach (var actor in data.Objects.Where(a => a.IsCharacter)) oldItem = state.ModelData.Item(slot);
_metaService.SetWeaponState(actor, value); if (!state.CanUnlock(key))
return false;
state.ModelData.SetItem(slot, item);
state[slot, false] = source;
return true;
}
/// <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)
{
oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
state[slot, false] = source;
state[slot, true] = source;
return true;
}
/// <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)
{
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetStain(slot, stain);
state[slot, true] = source;
return true;
}
public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
uint key = 0)
{
(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."),
};
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.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -30,6 +31,7 @@ public class StateListener : IDisposable
private readonly WeaponVisibilityChanged _weaponVisibility; private readonly WeaponVisibilityChanged _weaponVisibility;
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly FunModule _funModule; private readonly FunModule _funModule;
private readonly HumanModelList _humans;
public bool Enabled public bool Enabled
{ {
@ -39,7 +41,7 @@ public class StateListener : IDisposable
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config, public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule) HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans)
{ {
_manager = manager; _manager = manager;
_items = items; _items = items;
@ -53,6 +55,7 @@ public class StateListener : IDisposable
_headGearVisibility = headGearVisibility; _headGearVisibility = headGearVisibility;
_autoDesignApplier = autoDesignApplier; _autoDesignApplier = autoDesignApplier;
_funModule = funModule; _funModule = funModule;
_humans = humans;
if (Enabled) if (Enabled)
Subscribe(); Subscribe();
@ -320,10 +323,11 @@ public class StateListener : IDisposable
// Model ID did change, reload entire state accordingly. // Model ID did change, reload entire state accordingly.
// Always use the actor for the base data. // Always use the actor for the base data.
if (modelId == 0) var isHuman = _humans.IsHuman(modelId);
state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, (byte*)equipData); if (isHuman)
else
state.BaseData = _manager.FromActor(actor, false); state.BaseData = _manager.FromActor(actor, false);
else
state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, (byte*)equipData);
return UpdateState.Change; return UpdateState.Change;
} }

View file

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