feat: add configuration for emotes

This commit is contained in:
Anna 2024-07-22 05:49:51 -04:00
parent a08c086323
commit 34be1311d5
Signed by: anna
GPG Key ID: D0943384CD9F87D1
5 changed files with 83 additions and 36 deletions

View File

@ -18,6 +18,7 @@ public class Configuration : IPluginConfiguration {
public bool LockViewer; public bool LockViewer;
public bool ClickThroughViewer; public bool ClickThroughViewer;
public bool HideTitlebar; public bool HideTitlebar;
public bool ShowEmotes = true;
public float ViewerOpacity = 100.0f; public float ViewerOpacity = 100.0f;
public int DefaultGlyph = 3; public int DefaultGlyph = 3;
} }

View File

@ -77,7 +77,7 @@ public class MessageWithTerritory {
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public class EmoteData { public class EmoteData {
public required uint Id { get; set; } public required uint Id { get; set; }
public required byte[] Customise { get; set; } public required List<byte> Customise { get; set; }
public required EquipmentData[] Equipment { get; set; } public required EquipmentData[] Equipment { get; set; }
public required WeaponData[] Weapon { get; set; } public required WeaponData[] Weapon { get; set; }
public required uint Glasses { get; set; } public required uint Glasses { get; set; }
@ -91,7 +91,9 @@ public class EmoteData {
public class EquipmentData { public class EquipmentData {
public required ushort Id { get; set; } public required ushort Id { get; set; }
public required byte Variant { get; set; } public required byte Variant { get; set; }
[JsonProperty("stain_0")]
public required byte Stain0 { get; set; } public required byte Stain0 { get; set; }
[JsonProperty("stain_1")]
public required byte Stain1 { get; set; } public required byte Stain1 { get; set; }
public required ulong Value { get; set; } public required ulong Value { get; set; }
} }
@ -101,7 +103,9 @@ public class EquipmentData {
public class WeaponData { public class WeaponData {
public required WeaponModelId ModelId { get; set; } public required WeaponModelId ModelId { get; set; }
public required byte State { get; set; } public required byte State { get; set; }
[JsonProperty("flags_1")]
public required ushort Flags1 { get; set; } public required ushort Flags1 { get; set; }
[JsonProperty("flags_2")]
public required byte Flags2 { get; set; } public required byte Flags2 { get; set; }
} }
@ -111,7 +115,9 @@ public class WeaponModelId {
public required ushort Id { get; set; } public required ushort Id { get; set; }
public required ushort Kind { get; set; } public required ushort Kind { get; set; }
public required ushort Variant { get; set; } public required ushort Variant { get; set; }
[JsonProperty("stain_0")]
public required byte Stain0 { get; set; } public required byte Stain0 { get; set; }
[JsonProperty("stain_1")]
public required byte Stain1 { get; set; } public required byte Stain1 { get; set; }
public required ulong Value { get; set; } public required ulong Value { get; set; }
} }

View File

@ -121,6 +121,7 @@ internal class Settings : ITab {
anyChanged |= vfx |= ImGui.Checkbox("Disable in cutscenes", ref this.Plugin.Config.DisableInCutscene); anyChanged |= vfx |= ImGui.Checkbox("Disable in cutscenes", ref this.Plugin.Config.DisableInCutscene);
anyChanged |= vfx |= ImGui.Checkbox("Disable in /gpose", ref this.Plugin.Config.DisableInGpose); anyChanged |= vfx |= ImGui.Checkbox("Disable in /gpose", ref this.Plugin.Config.DisableInGpose);
anyChanged |= vfx |= ImGui.Checkbox("Remove glow effect from signs", ref this.Plugin.Config.RemoveGlow); anyChanged |= vfx |= ImGui.Checkbox("Remove glow effect from signs", ref this.Plugin.Config.RemoveGlow);
anyChanged |= ImGui.Checkbox("Show player emotes", ref this.Plugin.Config.ShowEmotes);
var tt = this.Plugin.DataManager.GetExcelSheet<TerritoryType>(); var tt = this.Plugin.DataManager.GetExcelSheet<TerritoryType>();
if (tt == null) { if (tt == null) {

View File

@ -3,6 +3,7 @@ using System.Text;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Utility;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
@ -28,6 +29,7 @@ internal class Write : ITab {
private (int, int) _word2 = (-1, -1); private (int, int) _word2 = (-1, -1);
private int _glyph; private int _glyph;
private int _emoteIdx = -1; private int _emoteIdx = -1;
private string _emoteSearch = string.Empty;
private const string Placeholder = "****"; private const string Placeholder = "****";
private Pack? Pack => Pack.All.Get(this._pack); private Pack? Pack => Pack.All.Get(this._pack);
@ -69,6 +71,7 @@ internal class Write : ITab {
this.Emotes = this.Plugin.DataManager.GetExcelSheet<Emote>()! this.Emotes = this.Plugin.DataManager.GetExcelSheet<Emote>()!
.Skip(1) .Skip(1)
.Where(emote => emote.TextCommand.Row != 0)
.ToList(); .ToList();
this._glyph = this.Plugin.Config.DefaultGlyph; this._glyph = this.Plugin.Config.DefaultGlyph;
@ -312,30 +315,50 @@ internal class Write : ITab {
var emoteLabel = this._emoteIdx == -1 var emoteLabel = this._emoteIdx == -1
? "None" ? "None"
: this.Emotes[this._emoteIdx].Name.ToDalamudString().TextValue; : this.Emotes[this._emoteIdx].Name.ToDalamudString().TextValue;
if (ImGui.BeginCombo("Emote", emoteLabel)) { if (ImGui.BeginCombo("Emote", emoteLabel, ImGuiComboFlags.HeightLarge)) {
using var endCombo = new OnDispose(ImGui.EndCombo); using var endCombo = new OnDispose(ImGui.EndCombo);
if (ImGui.Selectable("None##no-emote", this._emoteIdx == -1)) { if (ImGui.IsWindowAppearing()) {
this._emoteIdx = -1; ImGui.SetKeyboardFocusHere();
} }
ImGui.Separator(); ImGui.SetNextItemWidth(-1);
ImGui.InputTextWithHint("###emote-search", "Search...", ref this._emoteSearch, 100, ImGuiInputTextFlags.AutoSelectAll);
for (var i = 0; i < this.Emotes.Count; i++) { using var endChild = new OnDispose(ImGui.EndChild);
var emote = this.Emotes[i]; if (ImGui.BeginChild("##emote-search-child", new Vector2(0, 150) * ImGuiHelpers.GlobalScale)) {
var name = emote.Name.ToDalamudString().TextValue; if (ImGui.Selectable("None##no-emote", this._emoteIdx == -1)) {
this._emoteIdx = -1;
var unlocked = IsEmoteUnlocked(emote); ImGui.CloseCurrentPopup();
if (!unlocked) {
ImGui.BeginDisabled();
} }
if (ImGui.Selectable($"{name}##emote-{emote.RowId}", this._emoteIdx == i)) { ImGui.Separator();
this._emoteIdx = i;
}
if (!unlocked) { for (var i = 0; i < this.Emotes.Count; i++) {
ImGui.EndDisabled(); var emote = this.Emotes[i];
if (!string.IsNullOrEmpty(this._emoteSearch)) {
if (!emote.Name.ToDalamudString().TextValue.Contains(this._emoteSearch, StringComparison.InvariantCultureIgnoreCase)) {
if (!emote.TextCommand.Value!.Command.ToDalamudString().TextValue.Contains(this._emoteSearch, StringComparison.InvariantCultureIgnoreCase)) {
continue;
}
}
}
var name = emote.Name.ToDalamudString().TextValue;
var unlocked = IsEmoteUnlocked(emote);
if (!unlocked) {
ImGui.BeginDisabled();
}
if (ImGui.Selectable($"{name}##emote-{emote.RowId}", this._emoteIdx == i)) {
this._emoteIdx = i;
ImGui.CloseCurrentPopup();
}
if (!unlocked) {
ImGui.EndDisabled();
}
} }
} }
} }
@ -416,12 +439,11 @@ internal class Write : ITab {
} }
private unsafe EmoteData GetEmoteData(Emote emote, IPlayerCharacter player) { private unsafe EmoteData GetEmoteData(Emote emote, IPlayerCharacter player) {
var objMan = ClientObjectManager.Instance(); var chara = (Character*) GameObjectManager.Instance()->Objects.GetObjectByGameObjectId(player.GameObjectId);
var chara = (BattleChara*) objMan->GetObjectByIndex(player.ObjectIndex);
return new EmoteData { return new EmoteData {
Id = emote.RowId, Id = emote.RowId,
Customise = player.Customize, Customise = player.Customize.ToList(),
Equipment = chara->DrawData.EquipmentModelIds Equipment = chara->DrawData.EquipmentModelIds
.ToArray() .ToArray()
.Select(equip => new EquipmentData { .Select(equip => new EquipmentData {
@ -464,6 +486,8 @@ internal class Write : ITab {
this._word1 = (-1, -1); this._word1 = (-1, -1);
this._word2 = (-1, -1); this._word2 = (-1, -1);
this._glyph = this.Plugin.Config.DefaultGlyph; this._glyph = this.Plugin.Config.DefaultGlyph;
this._emoteIdx = -1;
this._emoteSearch = string.Empty;
} }
private void ClearIfNecessary() { private void ClearIfNecessary() {

View File

@ -19,6 +19,14 @@ internal class ActorManager : IDisposable {
public void Dispose() { public void Dispose() {
this.Plugin.Ui.Viewer.View -= this.OnView; this.Plugin.Ui.Viewer.View -= this.OnView;
this.Plugin.Framework.Update -= this.OnFramework; this.Plugin.Framework.Update -= this.OnFramework;
if (this._idx != null) {
unsafe {
var objMan = ClientObjectManager.Instance();
new DisableAction().Run(this, objMan);
new DeleteAction().Run(this, objMan);
}
}
} }
private unsafe void OnFramework(IFramework framework) { private unsafe void OnFramework(IFramework framework) {
@ -52,7 +60,7 @@ internal class ActorManager : IDisposable {
Plugin.Log.Debug($"OnView message is {msg}"); Plugin.Log.Debug($"OnView message is {msg}");
this.Despawn(); this.Despawn();
if (message?.Emote != null) { if (this.Plugin.Config.ShowEmotes && message?.Emote != null) {
this.Spawn(message); this.Spawn(message);
} }
} }
@ -122,6 +130,7 @@ internal class ActorManager : IDisposable {
manager._idx = idx; manager._idx = idx;
var emote = message.Emote; var emote = message.Emote;
var emoteRow = manager.GetValidEmote(emote.Id);
var chara = (BattleChara*) objMan->GetObjectByIndex((ushort) idx); var chara = (BattleChara*) objMan->GetObjectByIndex((ushort) idx);
@ -131,7 +140,7 @@ internal class ActorManager : IDisposable {
chara->Rotation = message.Yaw; chara->Rotation = message.Yaw;
var drawData = &chara->DrawData; var drawData = &chara->DrawData;
var maxLen = Math.Min(sizeof(CustomizeData), emote.Customise.Length); var maxLen = Math.Min(sizeof(CustomizeData), emote.Customise.Count);
var rawCustomise = (byte*) &drawData->CustomizeData; var rawCustomise = (byte*) &drawData->CustomizeData;
for (var i = 0; i < maxLen; i++) { for (var i = 0; i < maxLen; i++) {
rawCustomise[i] = emote.Customise[i]; rawCustomise[i] = emote.Customise[i];
@ -144,26 +153,23 @@ internal class ActorManager : IDisposable {
Variant = equip.Variant, Variant = equip.Variant,
Stain0 = equip.Stain0, Stain0 = equip.Stain0,
Stain1 = equip.Stain1, Stain1 = equip.Stain1,
Value = equip.Value,
}; };
} }
for (var i = 0; i < Math.Min(drawData->WeaponData.Length, emote.Weapon.Length); i++) { if (emoteRow is { DrawsWeapon: true }) {
var weapon = emote.Weapon[i]; for (var i = 0; i < Math.Min(drawData->WeaponData.Length, emote.Weapon.Length); i++) {
drawData->Weapon((DrawDataContainer.WeaponSlot) i) = new DrawObjectData { var weapon = emote.Weapon[i];
ModelId = new FFXIVClientStructs.FFXIV.Client.Game.Character.WeaponModelId { drawData->Weapon((DrawDataContainer.WeaponSlot) i).ModelId = new FFXIVClientStructs.FFXIV.Client.Game.Character.WeaponModelId {
Id = weapon.ModelId.Id, Id = weapon.ModelId.Id,
Type = weapon.ModelId.Kind, Type = weapon.ModelId.Kind,
Variant = weapon.ModelId.Variant, Variant = weapon.ModelId.Variant,
Stain0 = weapon.ModelId.Stain0, Stain0 = weapon.ModelId.Stain0,
Stain1 = weapon.ModelId.Stain1, Stain1 = weapon.ModelId.Stain1,
Value = weapon.ModelId.Value, };
}, drawData->Weapon((DrawDataContainer.WeaponSlot) i).Flags1 = weapon.Flags1;
DrawObject = chara->DrawObject, drawData->Weapon((DrawDataContainer.WeaponSlot) i).Flags2 = weapon.Flags2;
Flags1 = weapon.Flags1, drawData->Weapon((DrawDataContainer.WeaponSlot) i).State = weapon.State;
Flags2 = weapon.Flags2, }
State = weapon.State,
};
} }
drawData->IsHatHidden = emote.HatHidden; drawData->IsHatHidden = emote.HatHidden;
@ -174,8 +180,8 @@ internal class ActorManager : IDisposable {
chara->Alpha = 0.25f; chara->Alpha = 0.25f;
chara->SetMode(CharacterModes.AnimLock, 0); chara->SetMode(CharacterModes.AnimLock, 0);
if (manager.Plugin.DataManager.GetExcelSheet<Emote>()?.GetRow(emote.Id) is { } row) { if (emoteRow != null) {
chara->Timeline.BaseOverride = (ushort) row.ActionTimeline[0].Row; chara->Timeline.BaseOverride = (ushort) emoteRow.ActionTimeline[0].Row;
} }
manager._tasks.Enqueue(new EnableAction()); manager._tasks.Enqueue(new EnableAction());
@ -183,6 +189,15 @@ internal class ActorManager : IDisposable {
} }
} }
private Emote? GetValidEmote(uint rowId) {
var emote = this.Plugin.DataManager.GetExcelSheet<Emote>()?.GetRow(rowId);
if (emote == null) {
return null;
}
return emote.TextCommand.Row == 0 ? null : emote;
}
private unsafe class EnableAction : BaseActorAction { private unsafe class EnableAction : BaseActorAction {
public override bool Run(ActorManager manager, ClientObjectManager* objMan) { public override bool Run(ActorManager manager, ClientObjectManager* objMan) {
if (!this.TryGetBattleChara(manager, objMan, out var chara)) { if (!this.TryGetBattleChara(manager, objMan, out var chara)) {