Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for facewear changing #64

Merged
merged 3 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading