Skip to content

Commit

Permalink
Add support for facewear changing (#64)
Browse files Browse the repository at this point in the history
* Add support for facewear changing

* Cleanup

* Facewear icon
  • Loading branch information
AsgardXIV authored Jul 10, 2024
1 parent 510288b commit 323e9f4
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 14 deletions.
1 change: 1 addition & 0 deletions Brio/Capabilities/Debug/DebugCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public IReadOnlyDictionary<string, nint> GetInterestingAddresses()
["CameraManager"] = ((nint)CameraManager.Instance()),
["ActiveCamera"] = ((nint)CameraManager.Instance()->GetActiveCamera()),
["EventFramework"] = ((nint)EventFramework.Instance()),
["Target"] = ((nint)TargetSystem.Instance()->Target),
};

return addresses.AsReadOnly();
Expand Down
7 changes: 7 additions & 0 deletions Brio/Files/AnamnesisCharaFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ internal class AnamnesisCharaFile : JsonDocumentBase
public float Transparency { get; set; }
public float MuscleTone { get; set; }
public float HeightMultiplier { get; set; }
public byte Glasses { get; set; }

public override void GetAutoTags(ref TagCollection tags)
{
Expand Down Expand Up @@ -149,6 +150,9 @@ public static implicit operator ActorAppearance(AnamnesisCharaFile chara)
appearance.Equipment.LFinger = chara.LeftRing;
appearance.Equipment.RFinger = chara.RightRing;

// Facewear
appearance.Facewear = chara.Glasses;

// Extended Appearance
appearance.ExtendedAppearance.Transparency = chara.Transparency;

Expand Down Expand Up @@ -206,6 +210,9 @@ public static implicit operator AnamnesisCharaFile(ActorAppearance appearance)
LeftRing = appearance.Equipment.LFinger,
RightRing = appearance.Equipment.RFinger,

// Facewear
Glasses = appearance.Facewear,

// Extended Appearance
Transparency = appearance.ExtendedAppearance.Transparency
};
Expand Down
18 changes: 18 additions & 0 deletions Brio/Game/Actor/ActorAppearanceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ internal class ActorAppearanceService : IDisposable
private unsafe delegate nint UpdateTintDelegate(nint charaBase, nint tint);
private readonly Hook<UpdateTintDelegate> _updateTintHook = null!;

private unsafe delegate* unmanaged<DrawDataContainer*, byte, byte, void> _setFacewear;

private uint _forceNpcHackCount = 0;

public bool CanTint => _configurationService.Configuration.Appearance.EnableTinting;
Expand All @@ -58,6 +60,9 @@ public unsafe ActorAppearanceService(GPoseService gPoseService, ConfigurationSer
var updateTintHook = Marshal.ReadInt64((nint)(CharacterBase.StaticVirtualTablePointer) + 0xC0);
_updateTintHook = hooks.HookFromAddress<UpdateTintDelegate>((nint)updateTintHook, UpdateTintDetour);
_updateTintHook.Enable();

var setFacewearAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 FF C5 41 83 FD ?? 72");
_setFacewear = (delegate* unmanaged<DrawDataContainer*, byte, byte, void>)setFacewearAddress;
}

public void PushForceNpcHack() => ++_forceNpcHackCount;
Expand Down Expand Up @@ -182,6 +187,19 @@ public async Task<RedrawResult> SetCharacterAppearance(ICharacter character, Act
}
}
}

// Facewear
if(existingAppearance.Facewear != appearance.Facewear)
{
if(needsRedraw)
{
character.BrioDrawData()->Facewear = appearance.Facewear;
}
else
{
_setFacewear(&native->DrawData, 0, appearance.Facewear);
}
}
}

if(options.HasFlag(AppearanceImportOptions.Weapon))
Expand Down
8 changes: 8 additions & 0 deletions Brio/Game/Actor/Appearance/ActorAppearance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Brio.Game.Actor.Appearance;
internal struct ActorAppearance()
{
public int ModelCharaId;
public byte Facewear;
public ActorWeapons Weapons = new();
public ActorEquipment Equipment = new();
public ActorCustomize Customize = new();
Expand All @@ -30,6 +31,8 @@ public unsafe static ActorAppearance FromCharacter(DalamudCharacter character)
actorAppearance.Equipment = *(ActorEquipment*)slot;
}

actorAppearance.Facewear = character.BrioDrawData()->Facewear;

actorAppearance.Customize = *(ActorCustomize*)&native->DrawData.CustomizeData;

actorAppearance.Runtime.IsHatHidden = native->DrawData.IsHatHidden;
Expand Down Expand Up @@ -116,6 +119,9 @@ public static ActorAppearance FromBNpc(BNpcBase npc)
actorAppearance.Equipment = equipment;
}

// TODO: Can NPCs have facewear?
actorAppearance.Facewear = 0;

return actorAppearance;
}

Expand Down Expand Up @@ -270,6 +276,8 @@ public static ActorAppearance FromENpc(ENpcBase npc)
actorAppearance.Equipment.LFinger.Stain1 = (byte)npc.Dye2LeftRing.Row;
}

// TODO: Can NPCs have facewear?
actorAppearance.Facewear = 0;

return actorAppearance;
}
Expand Down
5 changes: 5 additions & 0 deletions Brio/Game/Actor/Extensions/CharacterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ internal static class CharacterExtensions
return (StructsCharacter*)go.Address;
}

public unsafe static BrioDrawData* BrioDrawData(this ICharacter go)
{
return (BrioDrawData*)&go.Native()->DrawData;
}

public unsafe static bool HasCompanionSlot(this ICharacter go)
{
var native = go.Native();
Expand Down
14 changes: 14 additions & 0 deletions Brio/Game/Actor/Interop/BrioDrawData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using System.Runtime.InteropServices;

namespace Brio.Game.Actor.Interop;

[StructLayout(LayoutKind.Explicit)]
internal unsafe struct BrioDrawData
{
[FieldOffset(0x0)]
public DrawDataContainer DawData;

[FieldOffset(0x1D0)]
public byte Facewear;
}
33 changes: 33 additions & 0 deletions Brio/Game/Types/FacewearTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Brio.Resources;
using Lumina.Excel.GeneratedSheets2;
using OneOf;
using OneOf.Types;

namespace Brio.Game.Types;

[GenerateOneOf]
internal partial class FacewearUnion : OneOfBase<Glasses, None>
{
public static implicit operator FacewearUnion(FacewearId facewearId)
{
if(facewearId.Id != 0 && GameDataProvider.Instance.Glasses.TryGetValue(facewearId.Id, out var glasses))
return new FacewearUnion(glasses);

return new None();
}
}

internal record struct FacewearId(byte Id)
{
public static FacewearId None { get; } = new(0);

public static implicit operator FacewearId(DyeUnion dyeUnion) => dyeUnion.Match(
dyeRow => new FacewearId((byte)dyeRow.RowId),
none => None
);

public static implicit operator FacewearId(int dye) => new((byte)dye);
public static implicit operator FacewearId(byte dye) => new(dye);
public static implicit operator byte(FacewearId id) => id.Id;
public static implicit operator int(FacewearId id) => id.Id;
}
17 changes: 7 additions & 10 deletions Brio/IPC/GlamourerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
using Brio.Game.GPose;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Api.IpcSubscribers;
using System;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -73,12 +70,12 @@ bool ConnectToGlamourer()
return false;
}

var (major, minor) = _glamourerApiVersions.Invoke();
if(major != GlamourerApiMajor || minor < GlamourerApiMinor)
{
Brio.Log.Warning($"Glamourer API mismatch, found v{major}.{minor}");
return false;
}
var (major, minor) = _glamourerApiVersions.Invoke();
if(major != GlamourerApiMajor || minor < GlamourerApiMinor)
{
Brio.Log.Warning($"Glamourer API mismatch, found v{major}.{minor}");
return false;
}


Brio.Log.Debug("Glamourer integration initialized");
Expand All @@ -101,7 +98,7 @@ public Task RevertCharacter(ICharacter? character)
Brio.Log.Error("Starting glamourer revert...");


_glamourerRevertCharacter.Invoke(character!.ObjectIndex, character.DataId);
_glamourerRevertCharacter.Invoke(character!.ObjectIndex, character.DataId);

return _framework.RunOnTick(async () =>
{
Expand Down
2 changes: 1 addition & 1 deletion Brio/Library/Sources/FileSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ private IDalamudTextureWrap GetIcon()

private IDalamudTextureWrap? GetPreviewImage()
{
if(_isPreviewImageDisposed)
if(_isPreviewImageDisposed)
return null;

if(_previewImage == null || _previewImage.ImGuiHandle == 0)
Expand Down
Binary file added Brio/Resources/Embedded/Images/Facewear.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 65 additions & 1 deletion Brio/UI/Controls/Editors/GearEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ namespace Brio.UI.Controls.Editors;

internal class GearEditor()
{
private Vector2 IconSize => new(ImGui.GetTextLineHeight() * 4f);
private Vector2 IconSize => new(ImGui.GetTextLineHeight() * 3.9f);

private ActorAppearanceCapability _capability = null!;

private static readonly DyeSelector _dye0Selector = new("dye_0_selector");
private static readonly DyeSelector _dye1Selector = new("dye_1_selector");
private static readonly GearSelector _gearSelector = new("gear_selector");
private static readonly FacewearSelector _facewearSelector = new("facewear_selector");

private const ActorEquipSlot _weaponSlots = ActorEquipSlot.MainHand | ActorEquipSlot.OffHand;

Expand Down Expand Up @@ -63,6 +64,7 @@ public bool DrawGear(ref ActorAppearance currentAppearance, ActorAppearance orig
didChange |= DrawGearSlot(ref currentAppearance, ref currentAppearance.Equipment.Arms, ActorEquipSlot.Hands);
didChange |= DrawGearSlot(ref currentAppearance, ref currentAppearance.Equipment.Legs, ActorEquipSlot.Legs);
didChange |= DrawGearSlot(ref currentAppearance, ref currentAppearance.Equipment.Feet, ActorEquipSlot.Feet);
didChange |= DrawFacewearSlot(ref currentAppearance);
}
}

Expand Down Expand Up @@ -409,4 +411,66 @@ private bool DrawWeaponSlot(ref ActorAppearance appearance, ref WeaponModelId eq

return didChange;
}

private bool DrawFacewearSlot(ref ActorAppearance appearance)
{
bool didChange = false;

Vector2 faceIconSize = new Vector2(ImGui.GetTextLineHeight() * 2.3f);

FacewearUnion facewearUnion = new FacewearId(appearance.Facewear);
var (facewearId, facewearName, facewearIcon) = facewearUnion.Match(
glasses => ((byte)glasses.RowId, glasses.Unknown3, (uint)glasses.Unknown11),
none => ((byte)0, "None", (uint)0x0)
);

using(ImRaii.PushId("facewear"))
{
if(ImBrio.BorderedGameIcon("##icon", facewearIcon, "Images.Facewear.png", size: faceIconSize))
{
_facewearSelector.Select(facewearUnion, true);
ImGui.OpenPopup("facewear_popup");
}

ImGui.SameLine();

ImGui.SetCursorPosX(IconSize.X + (ImGui.GetStyle().FramePadding.X * 2f));

using(var group = ImRaii.Group())
{
if(group.Success)
{
string description = $"Facewear: {facewearName}";

ImGui.Text(description);

ImGui.SetNextItemWidth(ImGui.CalcTextSize("XXXXX").X);
int value = facewearId;
if(ImGui.InputInt("##facewearid", ref value, 0, 0, ImGuiInputTextFlags.EnterReturnsTrue))
{
appearance.Facewear = (byte)value;
didChange |= true;
}
}
}

using(var facewearPopup = ImRaii.Popup("facewear_popup"))
{
if(facewearPopup.Success)
{
_facewearSelector.Draw();
if(_facewearSelector.SoftSelectionChanged && _facewearSelector.SoftSelected != null)
{
appearance.Facewear = _facewearSelector.SoftSelected.Match(glasses => (byte)glasses.RowId, none => (byte)0);
didChange |= true;
}
if(_gearSelector.SelectionChanged)
ImGui.CloseCurrentPopup();

}
}
}

return didChange;
}
}
Loading

0 comments on commit 323e9f4

Please sign in to comment.