mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-29 20:03:45 +01:00
.
This commit is contained in:
parent
63e82d19dc
commit
e57538561f
34 changed files with 2428 additions and 720 deletions
|
|
@ -3,17 +3,12 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -30,114 +25,20 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
private readonly ObjectManager _objects;
|
||||
private readonly StateEditor _editor;
|
||||
|
||||
private readonly PenumbraService _penumbra;
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
|
||||
|
||||
public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor, StateChanged @event,
|
||||
PenumbraService penumbra, ObjectManager objects, StateEditor editor)
|
||||
ObjectManager objects, StateEditor editor)
|
||||
{
|
||||
_actors = actors;
|
||||
_items = items;
|
||||
_customizations = customizations;
|
||||
_visor = visor;
|
||||
_event = @event;
|
||||
_penumbra = penumbra;
|
||||
_objects = objects;
|
||||
_editor = editor;
|
||||
}
|
||||
|
||||
public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state)
|
||||
=> GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out state);
|
||||
|
||||
public bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state)
|
||||
{
|
||||
if (TryGetValue(identifier, out state))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
var designData = FromActor(actor);
|
||||
state = new ActorState(identifier)
|
||||
{
|
||||
ModelData = designData,
|
||||
BaseData = designData,
|
||||
};
|
||||
_states.Add(identifier, state);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not create new actor data for {identifier}:\n{ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateEquip(ActorState state, EquipSlot slot, CharacterArmor armor)
|
||||
{
|
||||
var current = state.ModelData.Item(slot);
|
||||
if (armor.Set.Value != current.ModelId.Value || armor.Variant != current.Variant)
|
||||
{
|
||||
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
||||
state.ModelData.SetItem(slot, item);
|
||||
}
|
||||
|
||||
state.ModelData.SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
||||
public void UpdateWeapon(ActorState state, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
var current = state.ModelData.Item(slot);
|
||||
if (weapon.Set.Value != current.ModelId.Value || weapon.Variant != current.Variant || weapon.Type.Value != current.WeaponType.Value)
|
||||
{
|
||||
var item = _items.Identify(slot, weapon.Set, weapon.Type, (byte)weapon.Variant,
|
||||
slot == EquipSlot.OffHand ? state.ModelData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown);
|
||||
state.ModelData.SetItem(slot, item);
|
||||
}
|
||||
|
||||
state.ModelData.SetStain(slot, weapon.Stain);
|
||||
}
|
||||
|
||||
public unsafe void Update(ActorState state, Actor actor)
|
||||
{
|
||||
if (!actor.IsCharacter)
|
||||
return;
|
||||
|
||||
if (actor.AsCharacter->ModelCharaId != state.ModelData.ModelId)
|
||||
return;
|
||||
|
||||
var model = actor.Model;
|
||||
|
||||
state.ModelData.SetHatVisible(!actor.AsCharacter->DrawData.IsHatHidden);
|
||||
state.ModelData.SetIsWet(actor.AsCharacter->IsGPoseWet);
|
||||
state.ModelData.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
|
||||
|
||||
if (model.IsHuman)
|
||||
{
|
||||
var head = state.ModelData.IsHatVisible() ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
|
||||
UpdateEquip(state, EquipSlot.Head, head);
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
|
||||
UpdateEquip(state, slot, model.GetArmor(slot));
|
||||
|
||||
state.ModelData.Customize = model.GetCustomize();
|
||||
var (_, _, main, off) = model.GetWeapons(actor);
|
||||
UpdateWeapon(state, EquipSlot.MainHand, main);
|
||||
UpdateWeapon(state, EquipSlot.OffHand, off);
|
||||
state.ModelData.SetVisor(_visor.GetVisorState(model));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
UpdateEquip(state, slot, actor.GetArmor(slot));
|
||||
|
||||
state.ModelData.Customize = actor.GetCustomize();
|
||||
UpdateWeapon(state, EquipSlot.MainHand, actor.GetMainhand());
|
||||
UpdateWeapon(state, EquipSlot.OffHand, actor.GetOffhand());
|
||||
state.ModelData.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActorState>> GetEnumerator()
|
||||
=> _states.GetEnumerator();
|
||||
|
||||
|
|
@ -162,15 +63,54 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
public IEnumerable<ActorState> Values
|
||||
=> _states.Values;
|
||||
|
||||
/// <inheritdoc cref="GetOrCreate(ActorIdentifier, Actor, out ActorState?)"/>
|
||||
public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state)
|
||||
=> GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out state);
|
||||
|
||||
/// <summary> Try to obtain or create a new state for an existing actor. Returns false if no state could be created. </summary>
|
||||
public bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state)
|
||||
{
|
||||
if (TryGetValue(identifier, out state))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
var designData = FromActor(actor);
|
||||
// Initial Creation has identical base and model data.
|
||||
state = new ActorState(identifier)
|
||||
{
|
||||
ModelData = designData,
|
||||
BaseData = designData,
|
||||
};
|
||||
// state.Identifier is owned.
|
||||
_states.Add(state.Identifier, state);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not create new actor data for {identifier}:\n{ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create DesignData from a given actor.
|
||||
/// This uses the draw object if available and where possible,
|
||||
/// and the game object where necessary.
|
||||
/// </summary>
|
||||
public unsafe DesignData FromActor(Actor actor)
|
||||
{
|
||||
var ret = new DesignData();
|
||||
// If the given actor is not a character, just return a default character.
|
||||
if (!actor.IsCharacter)
|
||||
{
|
||||
ret.SetDefaultEquipment(_items);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Model ID is only unambiguously contained in the game object.
|
||||
// The draw object only has the object type.
|
||||
// TODO do this right.
|
||||
if (actor.AsCharacter->CharacterData.ModelCharaId != 0)
|
||||
{
|
||||
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData,
|
||||
|
|
@ -182,14 +122,23 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
CharacterWeapon main;
|
||||
CharacterWeapon off;
|
||||
|
||||
// Hat visibility is only unambiguously contained in the game object.
|
||||
// Set it first to know where to get head slot data from.
|
||||
ret.SetHatVisible(!actor.AsCharacter->DrawData.IsHatHidden);
|
||||
|
||||
// Use the draw object if it is a human.
|
||||
if (model.IsHuman)
|
||||
{
|
||||
// Customize can be obtained from the draw object.
|
||||
ret.Customize = model.GetCustomize();
|
||||
|
||||
// We can not use the head slot data from the draw object if the hat is hidden.
|
||||
var head = ret.IsHatVisible() ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
|
||||
var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant);
|
||||
ret.SetItem(EquipSlot.Head, headItem);
|
||||
ret.SetStain(EquipSlot.Head, head.Stain);
|
||||
|
||||
// The other slots can be used from the draw object.
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
|
||||
{
|
||||
var armor = model.GetArmor(slot);
|
||||
|
|
@ -198,12 +147,17 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
ret.SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
||||
ret.Customize = model.GetCustomize();
|
||||
// Weapons use the draw objects of the weapons, but require the game object either way.
|
||||
(_, _, 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));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Obtain all data from the game object.
|
||||
ret.Customize = actor.GetCustomize();
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var armor = actor.GetArmor(slot);
|
||||
|
|
@ -212,12 +166,12 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
ret.SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
||||
ret.Customize = actor.GetCustomize();
|
||||
main = actor.GetMainhand();
|
||||
off = actor.GetOffhand();
|
||||
main = actor.GetMainhand();
|
||||
off = actor.GetOffhand();
|
||||
ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
|
||||
}
|
||||
|
||||
// Set the weapons regardless of source.
|
||||
var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, (byte)main.Variant);
|
||||
var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, (byte)off.Variant, mainItem.Type);
|
||||
ret.SetItem(EquipSlot.MainHand, mainItem);
|
||||
|
|
@ -225,39 +179,232 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
ret.SetItem(EquipSlot.OffHand, offItem);
|
||||
ret.SetStain(EquipSlot.OffHand, off.Stain);
|
||||
|
||||
// Wetness can technically only be set in GPose or via external tools.
|
||||
// It is only available in the game object.
|
||||
ret.SetIsWet(actor.AsCharacter->IsGPoseWet);
|
||||
|
||||
// Weapon visibility could technically be inferred from the weapon draw objects,
|
||||
// but since we use hat visibility from the game object we can also use weapon visibility from it.
|
||||
ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#region Change Values
|
||||
|
||||
/// <summary> Change a customization value. </summary>
|
||||
public void ChangeCustomize(ActorState state, ActorData data, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source,
|
||||
bool force)
|
||||
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source)
|
||||
{
|
||||
ref var s = ref state[idx];
|
||||
if (s is StateChanged.Source.Fixed && source is StateChanged.Source.Game)
|
||||
return;
|
||||
|
||||
var oldValue = state.ModelData.Customize[idx];
|
||||
if (oldValue == value && !force)
|
||||
return;
|
||||
|
||||
// Update state data.
|
||||
var old = state.ModelData.Customize[idx];
|
||||
state.ModelData.Customize[idx] = value;
|
||||
state[idx] = source;
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"Changed customize {idx.ToDefaultName()} for {state.Identifier} ({string.Join(", ", data.Objects.Select(o => $"0x{o.Address}"))}) from {oldValue.Value} to {value.Value}.");
|
||||
_event.Invoke(StateChanged.Type.Customize, source, state, data, (oldValue, value, idx));
|
||||
// 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(
|
||||
$"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));
|
||||
}
|
||||
|
||||
/// <summary> Change an entire customization array according to flags. </summary>
|
||||
public void ChangeCustomize(ActorState state, in Customize customizeInput, CustomizeFlag apply, StateChanged.Source source)
|
||||
{
|
||||
// Update state data.
|
||||
var old = state.ModelData.Customize;
|
||||
var (customize, applied) = _customizations.Combine(state.ModelData.Customize, customizeInput, apply);
|
||||
if (applied == 0)
|
||||
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.
|
||||
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));
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 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;
|
||||
|
||||
// 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)
|
||||
_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(
|
||||
$"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));
|
||||
}
|
||||
|
||||
/// <summary> Change a single piece of equipment including stain. </summary>
|
||||
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source)
|
||||
{
|
||||
// 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;
|
||||
|
||||
// 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)
|
||||
_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(
|
||||
$"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));
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// Update state data.
|
||||
var old = state.ModelData.Stain(slot);
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state[slot, true] = 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.ChangeStain(objects, slot, stain);
|
||||
|
||||
// Meta.
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary> Change hat visibility. </summary>
|
||||
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source)
|
||||
{
|
||||
// Update state data.
|
||||
var old = state.ModelData.IsHatVisible();
|
||||
state.ModelData.SetHatVisible(value);
|
||||
state[ActorState.MetaFlag.HatState] = source;
|
||||
|
||||
// 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.
|
||||
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.MetaFlag.HatState));
|
||||
}
|
||||
|
||||
/// <summary> Change weapon visibility. </summary>
|
||||
public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source)
|
||||
{
|
||||
// Update state data.
|
||||
var old = state.ModelData.IsWeaponVisible();
|
||||
state.ModelData.SetWeaponVisible(value);
|
||||
state[ActorState.MetaFlag.WeaponState] = source;
|
||||
|
||||
// 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.
|
||||
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.MetaFlag.WeaponState));
|
||||
}
|
||||
|
||||
/// <summary> Change visor state. </summary>
|
||||
public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source)
|
||||
{
|
||||
// Update state data.
|
||||
var old = state.ModelData.IsVisorToggled();
|
||||
state.ModelData.SetVisor(value);
|
||||
state[ActorState.MetaFlag.VisorState] = 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.ChangeVisor(objects, value);
|
||||
|
||||
// Meta.
|
||||
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.MetaFlag.VisorState));
|
||||
}
|
||||
|
||||
/// <summary> Set GPose Wetness. </summary>
|
||||
public void ChangeWetness(ActorState state, bool value, StateChanged.Source source)
|
||||
{
|
||||
// Update state data.
|
||||
var old = state.ModelData.IsWet();
|
||||
state.ModelData.SetIsWet(value);
|
||||
state[ActorState.MetaFlag.Wetness] = source;
|
||||
|
||||
// Update draw objects / game objects.
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
_editor.ChangeWetness(objects, value);
|
||||
|
||||
// Meta.
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaFlag.Wetness], state, objects, (old, value, ActorState.MetaFlag.Wetness));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ApplyDesign(Design design, ActorState state)
|
||||
{
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
|
||||
{
|
||||
switch (design.DoApplyEquip(slot), design.DoApplyStain(slot))
|
||||
switch (applyPiece, applyStain)
|
||||
{
|
||||
case (false, false): continue;
|
||||
case (false, false): break;
|
||||
case (true, false):
|
||||
ChangeEquip(state, slot, design.DesignData.Item(slot), StateChanged.Source.Manual);
|
||||
ChangeItem(state, slot, design.DesignData.Item(slot), StateChanged.Source.Manual);
|
||||
break;
|
||||
case (false, true):
|
||||
ChangeStain(state, slot, design.DesignData.Stain(slot), StateChanged.Source.Manual);
|
||||
|
|
@ -267,6 +414,19 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
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));
|
||||
|
||||
if (design.DoApplyHatVisible())
|
||||
ChangeHatState(state, design.DesignData.IsHatVisible(), StateChanged.Source.Manual);
|
||||
if (design.DoApplyWeaponVisible())
|
||||
|
|
@ -274,17 +434,29 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
if (design.DoApplyVisorToggle())
|
||||
ChangeVisorState(state, design.DesignData.IsVisorToggled(), StateChanged.Source.Manual);
|
||||
if (design.DoApplyWetness())
|
||||
ChangeWetness(state, design.DesignData.IsWet());
|
||||
ChangeWetness(state, design.DesignData.IsWet(), StateChanged.Source.Manual);
|
||||
ChangeCustomize(state, design.DesignData.Customize, design.ApplyCustomize, StateChanged.Source.Manual);
|
||||
}
|
||||
|
||||
public void ResetState(ActorState state)
|
||||
{
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
ChangeEquip(state, slot, state.BaseData.Item(slot), state.BaseData.Stain(slot), StateChanged.Source.Game);
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
_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)
|
||||
|
|
@ -292,215 +464,24 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
if (!GetOrCreate(actor, out var state))
|
||||
return;
|
||||
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
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.ChangeCustomize(data, state.ModelData.Customize);
|
||||
if (customizeFlags.RequiresRedraw())
|
||||
return;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
_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));
|
||||
_editor.ChangeWetness(data, false);
|
||||
_editor.ChangeWeaponState(data, state.ModelData.IsWeaponVisible());
|
||||
_editor.ChangeHatState(data, state.ModelData.IsHatVisible());
|
||||
_editor.ChangeVisor(data, state.ModelData.IsVisorToggled());
|
||||
}
|
||||
|
||||
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.Item(slot);
|
||||
state.ModelData.SetItem(slot, item);
|
||||
state[slot, false] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} equipment piece in state {state.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {objects.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Equip, source, state, objects, (old, item, slot));
|
||||
}
|
||||
|
||||
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source)
|
||||
{
|
||||
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;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} equipment piece 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(StateChanged.Type.Equip, source, state, objects, (old, item, slot));
|
||||
_event.Invoke(StateChanged.Type.Stain, source, state, objects, (oldStain, stain, slot));
|
||||
}
|
||||
|
||||
public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.Stain(slot);
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state[slot, true] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
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));
|
||||
}
|
||||
|
||||
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.IsHatVisible();
|
||||
state.ModelData.SetHatVisible(value);
|
||||
state[ActorState.MetaFlag.HatState] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeHatState(objects, value);
|
||||
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.MetaFlag.HatState));
|
||||
}
|
||||
|
||||
public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.IsWeaponVisible();
|
||||
state.ModelData.SetWeaponVisible(value);
|
||||
state[ActorState.MetaFlag.WeaponState] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeWeaponState(objects, value);
|
||||
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.MetaFlag.WeaponState));
|
||||
}
|
||||
|
||||
public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.IsVisorToggled();
|
||||
state.ModelData.SetVisor(value);
|
||||
state[ActorState.MetaFlag.VisorState] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeVisor(objects, value);
|
||||
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.MetaFlag.VisorState));
|
||||
}
|
||||
|
||||
public void ChangeWetness(ActorState state, bool value)
|
||||
{
|
||||
var old = state.ModelData.IsWet();
|
||||
state.ModelData.SetIsWet(value);
|
||||
state[ActorState.MetaFlag.Wetness] = value ? StateChanged.Source.Manual : StateChanged.Source.Game;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
_editor.ChangeWetness(objects, value);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaFlag.Wetness], state, objects, (old, value, ActorState.MetaFlag.Wetness));
|
||||
}
|
||||
|
||||
//
|
||||
///// <summary> Change whether to apply a specific customize value. </summary>
|
||||
//public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||
//{
|
||||
// if (!design.SetApplyCustomize(idx, value))
|
||||
// return;
|
||||
//
|
||||
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||
// _saveService.QueueSave(design);
|
||||
// Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}.");
|
||||
// _event.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
|
||||
//}
|
||||
//
|
||||
|
||||
//
|
||||
///// <summary> Change a weapon. </summary>
|
||||
//public void ChangeWeapon(Design design, EquipSlot slot, EquipItem item)
|
||||
//{
|
||||
// var currentMain = design.DesignData.Item(EquipSlot.MainHand);
|
||||
// var currentOff = design.DesignData.Item(EquipSlot.OffHand);
|
||||
// switch (slot)
|
||||
// {
|
||||
// case EquipSlot.MainHand:
|
||||
// var newOff = currentOff;
|
||||
// if (item.Type == currentMain.Type)
|
||||
// {
|
||||
// if (_items.ValidateWeapons(item.Id, currentOff.Id, out _, out _).Length != 0)
|
||||
// return;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var newOffId = FullEquipTypeExtensions.OffhandTypes.Contains(item.Type)
|
||||
// ? item.Id
|
||||
// : ItemManager.NothingId(item.Type.Offhand());
|
||||
// if (_items.ValidateWeapons(item.Id, newOffId, out _, out newOff).Length != 0)
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// design.DesignData.SetItem(EquipSlot.MainHand, item);
|
||||
// design.DesignData.SetItem(EquipSlot.OffHand, newOff);
|
||||
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||
// _saveService.QueueSave(design);
|
||||
// Glamourer.Log.Debug(
|
||||
// $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.Id}) to {item.Name} ({item.Id}).");
|
||||
// _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff));
|
||||
// return;
|
||||
// case EquipSlot.OffHand:
|
||||
// if (item.Type != currentOff.Type)
|
||||
// return;
|
||||
// if (_items.ValidateWeapons(currentMain.Id, item.Id, out _, out _).Length > 0)
|
||||
// return;
|
||||
//
|
||||
// if (!design.DesignData.SetItem(EquipSlot.OffHand, item))
|
||||
// return;
|
||||
//
|
||||
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||
// _saveService.QueueSave(design);
|
||||
// Glamourer.Log.Debug(
|
||||
// $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.Id}) to {item.Name} ({item.Id}).");
|
||||
// _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
|
||||
// return;
|
||||
// default: return;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///// <summary> Change whether to apply a specific equipment piece. </summary>
|
||||
//public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
|
||||
//{
|
||||
// if (!design.SetApplyEquip(slot, value))
|
||||
// return;
|
||||
//
|
||||
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||
// _saveService.QueueSave(design);
|
||||
// Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
|
||||
// _event.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
|
||||
//}
|
||||
//
|
||||
///// <summary> Change the stain for any equipment piece. </summary>
|
||||
//public void ChangeStain(Design design, EquipSlot slot, StainId stain)
|
||||
//{
|
||||
// if (_items.ValidateStain(stain, out _).Length > 0)
|
||||
// return;
|
||||
//
|
||||
// var oldStain = design.DesignData.Stain(slot);
|
||||
// if (!design.DesignData.SetStain(slot, stain))
|
||||
// return;
|
||||
//
|
||||
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||
// _saveService.QueueSave(design);
|
||||
// Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Value}.");
|
||||
// _event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
|
||||
//}
|
||||
//
|
||||
///// <summary> Change whether to apply a specific stain. </summary>
|
||||
//public void ChangeApplyStain(Design design, EquipSlot slot, bool value)
|
||||
//{
|
||||
// if (!design.SetApplyStain(slot, value))
|
||||
// return;
|
||||
//
|
||||
// design.LastEdit = DateTimeOffset.UtcNow;
|
||||
// _saveService.QueueSave(design);
|
||||
// Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
|
||||
// _event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue