Skip to content

Commit

Permalink
Attachment system
Browse files Browse the repository at this point in the history
  • Loading branch information
AsgardXIV committed Nov 20, 2023
1 parent 7cb0c4e commit 1d5a7f6
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Brio/Game/Actor/ActorService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Brio.Core;
using Brio.Game.Actor.Extensions;
using Brio.Game.Actor.Interop;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using System;
Expand All @@ -23,6 +24,8 @@ public class ActorService : ServiceBase<ActorService>

private List<GameObject> _gposeActors = new();

public AttachmentInterop AttachmentInterop { get; } = new();

public ActorService()
{
var destroyAddress = Dalamud.SigScanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 81 C1");
Expand Down
7 changes: 5 additions & 2 deletions Brio/Game/Actor/ActorSpawnService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ private unsafe void ActorService_OnActorDestructing(DalamudGameObject gameObject
Character* originalPlayer = (Character*)localPlayer.AsNative();
if(originalPlayer == null) return null;

uint idCheck = com->CreateBattleCharacter();
bool reserveCompanionSlot = options.HasFlag(SpawnOptions.ReserveCompanionSlot);

uint idCheck = com->CreateBattleCharacter(param: (byte)(reserveCompanionSlot ? 1 : 0));
if(idCheck == 0xffffffff) return null;
ushort newId = (ushort)idCheck;

Expand Down Expand Up @@ -159,5 +161,6 @@ public override void Dispose()
public enum SpawnOptions
{
None = 0,
ApplyModelPosition = 1
ApplyModelPosition = 1,
ReserveCompanionSlot = 2,
}
24 changes: 24 additions & 0 deletions Brio/Game/Actor/Interop/AttachmentInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Dalamud.Utility.Signatures;
using static FFXIVClientStructs.FFXIV.Client.Game.Character.Character;

namespace Brio.Game.Actor.Interop;
public unsafe class AttachmentInterop
{
public delegate void CreateAndSetupMountDelegate(MountContainer* instance, short mountId, uint buddyModelTop, uint buddyModelBody, uint buddyModelLegs, byte buddyStain, byte unk6, byte unk7);
[Signature("E8 ?? ?? ?? ?? 8B 43 ?? 41 89 46", ScanType = ScanType.Text)]
public CreateAndSetupMountDelegate CreateAndSetupMount = null!;


public delegate void SetupOrnamentDelegate(OrnamentContainer* instance, short ornamentId, uint param);
[Signature("E8 ?? ?? ?? ?? 48 8B 7B ?? 0F B7 97", ScanType = ScanType.Text)]
public SetupOrnamentDelegate SetupOrnament = null!;

public delegate void SetupCompanionDelegate(CompanionContainer* instance, short companionId, uint param);
[Signature("E8 ?? ?? ?? ?? 84 C0 74 ?? 66 44 89 7F", ScanType = ScanType.Text)]
public SetupCompanionDelegate SetupCompanion = null!;

public AttachmentInterop()
{
Dalamud.GameInteropProvider.InitializeFromAttributes(this);
}
}
2 changes: 1 addition & 1 deletion Brio/UI/Components/Actor/ActorSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public unsafe void Draw()
{
var gposeObjects = ActorService.Instance.GPoseActors;

ImGui.Text($"Actors: {gposeObjects.Count}/{ActorService.GPoseActorCount}");
ImGui.Text($"Actors: {gposeObjects.Count}");

ImGui.PushItemWidth(-1);
if(ImGui.BeginListBox("###gpose_actor_list", new Vector2(-1, ImGui.GetTextLineHeight() * 9)))
Expand Down
20 changes: 19 additions & 1 deletion Brio/UI/Components/Actor/ActorTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Brio.UI.Components.Actor;
public static class ActorTab
{
private static ActorSelector _selector = new ActorSelector();
private static bool _allowCompanions = true;

public unsafe static void Draw()
{
Expand All @@ -30,7 +31,8 @@ public unsafe static void Draw()
if(!canSpawn) ImGui.BeginDisabled();
if(ImGui.Button("Spawn###gpose_actor_spawn"))
{
ushort? createdId = ActorSpawnService.Instance.Spawn();
SpawnOptions options = _allowCompanions ? SpawnOptions.ReserveCompanionSlot : SpawnOptions.None;
ushort? createdId = ActorSpawnService.Instance.Spawn(options);
if(createdId == null)
Dalamud.ToastGui.ShowError("Failed to Create Actor.");
}
Expand Down Expand Up @@ -61,6 +63,10 @@ public unsafe static void Draw()
{
ActorSpawnService.Instance.DestroyAll();
}

ImGui.Checkbox("Allow Attachments", ref _allowCompanions);
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Allow attachments to be attached to spawned actors.\nThis will take two slots instead of one so reduces the total actors you can spawn.");
}

if(ImGui.CollapsingHeader("Redraw"))
Expand Down Expand Up @@ -102,6 +108,18 @@ public unsafe static void Draw()
}
}

if(ImGui.CollapsingHeader("Attachments"))
{
if(_selector.SelectedObject != null)
{
AttachmentControls.Draw(_selector.SelectedObject);
}
else
{
ImGui.Text("No actor selected.");
}
}

if(ImGui.CollapsingHeader("Status Effects"))
{
if(_selector.SelectedObject != null)
Expand Down
209 changes: 209 additions & 0 deletions Brio/UI/Components/Actor/AttachmentControls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
using Brio.Game.Actor;
using Brio.Game.Actor.Extensions;
using Dalamud.Game.ClientState.Objects.Types;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using System.Linq;

namespace Brio.UI.Components.Actor;

// TODO: This whole thing is pretty ugly and there is a lot of business logic in the UI here.
// Once https://github.com/aers/FFXIVClientStructs/pull/708 is merged and available, I should abstract this better.
public unsafe static class AttachmentControls
{
private static string _searchTerm = string.Empty;
private static int _selectedEntry = 0;

public static void Draw(GameObject gameObject)
{
if(gameObject is Character managedChara)
{
DrawCharacter(managedChara);
}
else
{
ImGui.Text("Incompatible actor type.");
}
}

private static void DrawCharacter(Character character)
{
var chara = character.AsNative();
if(chara->CompanionObject == null)
{
ImGui.Text("Not compatible with attachments.");
return;
}

if(chara->Mount.MountObject != null)
{
ImGui.Text("Destroy: ");

ImGui.SameLine();

if(ImGui.Button("Mount###attachment_destroy_mount"))
{
ActorService.Instance.AttachmentInterop.CreateAndSetupMount(&chara->Mount, 0, 0, 0, 0, 0, 0, 0);
}
return;
}


if(chara->Ornament.OrnamentObject != null)
{
ImGui.Text("Destroy: ");

ImGui.SameLine();

if(ImGui.Button("Ornament###attachment_destroy_ornament"))
{
ActorService.Instance.AttachmentInterop.SetupOrnament(&chara->Ornament, 0, 0);
}
return;
}

if(chara->Companion.CompanionObject != null)
{
ImGui.Text("Destroy: ");

ImGui.SameLine();

if(ImGui.Button("Minion###attachment_destroy_companion"))
{
ActorService.Instance.AttachmentInterop.SetupCompanion(&chara->Companion, 0, 0);
}
return;
}

ImGui.Text("Create: ");

ImGui.SameLine();

if(ImGui.Button("Ornament###attachment_create_ornament"))
{
ImGui.OpenPopup("###global_ornament_selector");
}

ImGui.SameLine();

if(ImGui.Button("Mount###attachment_create_mount"))
{
ImGui.OpenPopup("###global_mount_selector");
}

ImGui.SameLine();

if(ImGui.Button("Minion###attachment_create_minion"))
{
ImGui.OpenPopup("###global_minion_selector");
}

if(ImGui.BeginPopup("###global_ornament_selector"))
{
ImGui.InputText("###global_ornament_search", ref _searchTerm, 64);

if(ImGui.BeginListBox("###global_ornament_listbox"))
{
var ornamentSheet = Dalamud.DataManager.Excel.GetSheet<Ornament>();
if(ornamentSheet != null)
{
var list = ornamentSheet.Where(i => !string.IsNullOrEmpty(i.Singular.RawString)).Where((i) => i.Singular.RawString.Contains(_searchTerm, System.StringComparison.CurrentCultureIgnoreCase)).ToList();
foreach(var ornament in list)
{
if(ImGui.Selectable($"{ornament.Singular} ({ornament.RowId})###global_ornament_{ornament.RowId}", _selectedEntry == ornament.RowId))
{
_selectedEntry = (int)ornament.RowId;
}

if(ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
{
ImGui.CloseCurrentPopup();

ActorService.Instance.AttachmentInterop.SetupOrnament(&chara->Ornament, (short)_selectedEntry, 0);

Dalamud.Framework.RunOnTick(() =>
{
chara->Ornament.OrnamentObject->Character.GameObject.EnableDraw();
}, delayTicks: 5);
}
}
}
ImGui.EndListBox();
}

ImGui.EndPopup();
}

if(ImGui.BeginPopup("###global_mount_selector"))
{
ImGui.InputText("###global_mount_search", ref _searchTerm, 64);

if(ImGui.BeginListBox("###global_mount_listbox"))
{
var mountSheet = Dalamud.DataManager.Excel.GetSheet<Mount>();
if(mountSheet != null)
{
var list = mountSheet.Where(i => !string.IsNullOrEmpty(i.Singular.RawString)).Where((i) => i.Singular.RawString.Contains(_searchTerm, System.StringComparison.CurrentCultureIgnoreCase)).ToList();
foreach(var mount in list)
{
if(ImGui.Selectable($"{mount.Singular} ({mount.RowId})###global_mount_{mount.RowId}", _selectedEntry == mount.RowId))
{
_selectedEntry = (int)mount.RowId;
}

if(ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
{
ImGui.CloseCurrentPopup();

ActorService.Instance.AttachmentInterop.CreateAndSetupMount(&chara->Mount, (short)_selectedEntry, 0, 0, 0, 0, 0, 0);

Dalamud.Framework.RunOnTick(() =>
{
chara->Mount.MountObject->GameObject.EnableDraw();
}, delayTicks: 5);
}
}
}
ImGui.EndListBox();
}

ImGui.EndPopup();
}

if(ImGui.BeginPopup("###global_minion_selector"))
{
ImGui.InputText("###global_minion_search", ref _searchTerm, 64);

if(ImGui.BeginListBox("###global_minion_listbox"))
{
var minionSheet = Dalamud.DataManager.Excel.GetSheet<Companion>();
if(minionSheet != null)
{
var list = minionSheet.Where(i => !string.IsNullOrEmpty(i.Singular.RawString)).Where((i) => i.Singular.RawString.Contains(_searchTerm, System.StringComparison.CurrentCultureIgnoreCase)).ToList();
foreach(var minion in list)
{
if(ImGui.Selectable($"{minion.Singular} ({minion.RowId})###global_minion_{minion.RowId}", _selectedEntry == minion.RowId))
{
_selectedEntry = (int)minion.RowId;
}

if(ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
{
ImGui.CloseCurrentPopup();

ActorService.Instance.AttachmentInterop.SetupCompanion(&chara->Companion, (short)_selectedEntry, 0);

Dalamud.Framework.RunOnTick(() =>
{
chara->Companion.CompanionObject->Character.GameObject.EnableDraw();
}, delayTicks: 5);
}
}
}
ImGui.EndListBox();
}

ImGui.EndPopup();
}
}
}

0 comments on commit 1d5a7f6

Please sign in to comment.