feat: send and receive emote data
This commit is contained in:
parent
fd0ca7f445
commit
a08c086323
@ -6,31 +6,30 @@ namespace OrangeGuidanceTomestone;
|
|||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class Message {
|
public class Message {
|
||||||
public Guid Id { get; init; }
|
public required Guid Id { get; init; }
|
||||||
public float X { get; init; }
|
public required float X { get; init; }
|
||||||
public float Y { get; init; }
|
public required float Y { get; init; }
|
||||||
public float Z { get; init; }
|
public required float Z { get; init; }
|
||||||
public float Yaw { get; init; }
|
public required float Yaw { get; init; }
|
||||||
|
|
||||||
[JsonProperty("message")]
|
[JsonProperty("message")]
|
||||||
public string Text { get; init; }
|
public required string Text { get; init; }
|
||||||
|
|
||||||
public int PositiveVotes { get; set; }
|
public required int PositiveVotes { get; set; }
|
||||||
public int NegativeVotes { get; set; }
|
public required int NegativeVotes { get; set; }
|
||||||
public int UserVote { get; set; }
|
public required int UserVote { get; set; }
|
||||||
|
|
||||||
public uint? Emote { get; set; }
|
public required EmoteData? Emote { get; set; }
|
||||||
public byte[]? Customise { get; set; }
|
|
||||||
|
|
||||||
public int Glyph { get; set; }
|
public required int Glyph { get; set; }
|
||||||
|
|
||||||
internal Vector3 Position => new(this.X, this.Y, this.Z);
|
internal Vector3 Position => new(this.X, this.Y, this.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class MessageWithTerritory {
|
public class MessageWithTerritory {
|
||||||
public Guid Id { get; init; }
|
public Guid Id { get; init; }
|
||||||
public uint Territory { get; init; }
|
public uint Territory { get; init; }
|
||||||
public uint? Ward { get; init; }
|
public uint? Ward { get; init; }
|
||||||
@ -48,8 +47,7 @@ internal class MessageWithTerritory {
|
|||||||
public int NegativeVotes { get; init; }
|
public int NegativeVotes { get; init; }
|
||||||
public int UserVote { get; set; }
|
public int UserVote { get; set; }
|
||||||
|
|
||||||
public uint? Emote { get; set; }
|
public EmoteData? Emote { get; set; }
|
||||||
public byte[]? Customise { get; set; }
|
|
||||||
|
|
||||||
public int Glyph { get; set; }
|
public int Glyph { get; set; }
|
||||||
public bool IsHidden { get; set; }
|
public bool IsHidden { get; set; }
|
||||||
@ -69,7 +67,6 @@ internal class MessageWithTerritory {
|
|||||||
NegativeVotes = message.NegativeVotes,
|
NegativeVotes = message.NegativeVotes,
|
||||||
UserVote = message.UserVote,
|
UserVote = message.UserVote,
|
||||||
Emote = message.Emote,
|
Emote = message.Emote,
|
||||||
Customise = message.Customise,
|
|
||||||
Glyph = message.Glyph,
|
Glyph = message.Glyph,
|
||||||
IsHidden = false,
|
IsHidden = false,
|
||||||
};
|
};
|
||||||
@ -78,56 +75,57 @@ internal class MessageWithTerritory {
|
|||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class EmoteData {
|
public class EmoteData {
|
||||||
public uint Id { get; set; }
|
public required uint Id { get; set; }
|
||||||
public byte[] Customise { get; set; }
|
public required byte[] Customise { get; set; }
|
||||||
public EquipmentData[] Equipment { get; set; }
|
public required EquipmentData[] Equipment { get; set; }
|
||||||
public WeaponData[] Weapon { get; set; }
|
public required WeaponData[] Weapon { get; set; }
|
||||||
public bool HatHidden { get; set; }
|
public required uint Glasses { get; set; }
|
||||||
public bool VisorToggled { get; set; }
|
public required bool HatHidden { get; set; }
|
||||||
public bool WeaponHidden { get; set; }
|
public required bool VisorToggled { get; set; }
|
||||||
|
public required bool WeaponHidden { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class EquipmentData {
|
public class EquipmentData {
|
||||||
public ushort Id { get; set; }
|
public required ushort Id { get; set; }
|
||||||
public byte Variant { get; set; }
|
public required byte Variant { get; set; }
|
||||||
public byte Stain0 { get; set; }
|
public required byte Stain0 { get; set; }
|
||||||
public byte Stain1 { get; set; }
|
public required byte Stain1 { get; set; }
|
||||||
public ulong Value { get; set; }
|
public required ulong Value { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class WeaponData {
|
public class WeaponData {
|
||||||
public WeaponModelId ModelId { get; set; }
|
public required WeaponModelId ModelId { get; set; }
|
||||||
public byte State { get; set; }
|
public required byte State { get; set; }
|
||||||
public ushort Flags1 { get; set; }
|
public required ushort Flags1 { get; set; }
|
||||||
public byte Flags2 { get; set; }
|
public required byte Flags2 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class WeaponModelId {
|
public class WeaponModelId {
|
||||||
public ushort Id { get; set; }
|
public required ushort Id { get; set; }
|
||||||
public ushort Type { get; set; }
|
public required ushort Kind { get; set; }
|
||||||
public ushort Variant { get; set; }
|
public required ushort Variant { get; set; }
|
||||||
public byte Stain0 { get; set; }
|
public required byte Stain0 { get; set; }
|
||||||
public byte Stain1 { get; set; }
|
public required byte Stain1 { get; set; }
|
||||||
public ulong Value { get; set; }
|
public required ulong Value { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class ErrorMessage {
|
public class ErrorMessage {
|
||||||
public string Code { get; set; }
|
public string Code { get; set; }
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
internal class MyMessages {
|
public class MyMessages {
|
||||||
public uint Extra { get; set; }
|
public uint Extra { get; set; }
|
||||||
public MessageWithTerritory[] Messages { get; set; }
|
public MessageWithTerritory[] Messages { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -6,35 +6,36 @@ namespace OrangeGuidanceTomestone;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
public class MessageRequest {
|
public class MessageRequest {
|
||||||
public uint Territory { get; set; }
|
public required uint Territory { get; set; }
|
||||||
public uint? World { get; set; }
|
public required uint? World { get; set; }
|
||||||
public uint? Ward { get; set; }
|
public required uint? Ward { get; set; }
|
||||||
public uint? Plot { get; set; }
|
public required uint? Plot { get; set; }
|
||||||
public float X { get; set; }
|
public required float X { get; set; }
|
||||||
public float Y { get; set; }
|
public required float Y { get; set; }
|
||||||
public float Z { get; set; }
|
public required float Z { get; set; }
|
||||||
public float Yaw { get; set; }
|
public required float Yaw { get; set; }
|
||||||
public Guid PackId { get; set; }
|
public required Guid PackId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("template_1")]
|
[JsonProperty("template_1")]
|
||||||
public int Template1 { get; set; }
|
public required int Template1 { get; set; }
|
||||||
|
|
||||||
[JsonProperty("word_1_list")]
|
[JsonProperty("word_1_list")]
|
||||||
public int? Word1List { get; set; }
|
public required int? Word1List { get; set; }
|
||||||
|
|
||||||
[JsonProperty("word_1_word")]
|
[JsonProperty("word_1_word")]
|
||||||
public int? Word1Word { get; set; }
|
public required int? Word1Word { get; set; }
|
||||||
|
|
||||||
public int? Conjunction { get; set; }
|
public required int? Conjunction { get; set; }
|
||||||
|
|
||||||
[JsonProperty("template_2")]
|
[JsonProperty("template_2")]
|
||||||
public int? Template2 { get; set; }
|
public required int? Template2 { get; set; }
|
||||||
|
|
||||||
[JsonProperty("word_2_list")]
|
[JsonProperty("word_2_list")]
|
||||||
public int? Word2List { get; set; }
|
public required int? Word2List { get; set; }
|
||||||
|
|
||||||
[JsonProperty("word_2_word")]
|
[JsonProperty("word_2_word")]
|
||||||
public int? Word2Word { get; set; }
|
public required int? Word2Word { get; set; }
|
||||||
|
|
||||||
public int Glyph { get; set; }
|
public required int Glyph { get; set; }
|
||||||
|
public required EmoteData? Emote { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Interface.Textures;
|
using Dalamud.Interface.Textures;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OrangeGuidanceTomestone.Helpers;
|
using OrangeGuidanceTomestone.Helpers;
|
||||||
using OrangeGuidanceTomestone.Util;
|
using OrangeGuidanceTomestone.Util;
|
||||||
@ -21,6 +27,7 @@ internal class Write : ITab {
|
|||||||
private int _part2 = -1;
|
private int _part2 = -1;
|
||||||
private (int, int) _word2 = (-1, -1);
|
private (int, int) _word2 = (-1, -1);
|
||||||
private int _glyph;
|
private int _glyph;
|
||||||
|
private int _emoteIdx = -1;
|
||||||
|
|
||||||
private const string Placeholder = "****";
|
private const string Placeholder = "****";
|
||||||
private Pack? Pack => Pack.All.Get(this._pack);
|
private Pack? Pack => Pack.All.Get(this._pack);
|
||||||
@ -30,6 +37,8 @@ internal class Write : ITab {
|
|||||||
private string? Word2 => this.GetWord(this._word2, this.Template2);
|
private string? Word2 => this.GetWord(this._word2, this.Template2);
|
||||||
private string? Conjunction => this.Pack?.Conjunctions?.Get(this._conj);
|
private string? Conjunction => this.Pack?.Conjunctions?.Get(this._conj);
|
||||||
|
|
||||||
|
private List<Emote> Emotes { get; }
|
||||||
|
|
||||||
private string? GetWord((int, int) word, Template? template) {
|
private string? GetWord((int, int) word, Template? template) {
|
||||||
if (word.Item2 == -1) {
|
if (word.Item2 == -1) {
|
||||||
return Placeholder;
|
return Placeholder;
|
||||||
@ -58,6 +67,10 @@ internal class Write : ITab {
|
|||||||
internal Write(Plugin plugin) {
|
internal Write(Plugin plugin) {
|
||||||
this.Plugin = plugin;
|
this.Plugin = plugin;
|
||||||
|
|
||||||
|
this.Emotes = this.Plugin.DataManager.GetExcelSheet<Emote>()!
|
||||||
|
.Skip(1)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
this._glyph = this.Plugin.Config.DefaultGlyph;
|
this._glyph = this.Plugin.Config.DefaultGlyph;
|
||||||
Pack.UpdatePacks();
|
Pack.UpdatePacks();
|
||||||
}
|
}
|
||||||
@ -296,6 +309,37 @@ internal class Write : ITab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emoteLabel = this._emoteIdx == -1
|
||||||
|
? "None"
|
||||||
|
: this.Emotes[this._emoteIdx].Name.ToDalamudString().TextValue;
|
||||||
|
if (ImGui.BeginCombo("Emote", emoteLabel)) {
|
||||||
|
using var endCombo = new OnDispose(ImGui.EndCombo);
|
||||||
|
|
||||||
|
if (ImGui.Selectable("None##no-emote", this._emoteIdx == -1)) {
|
||||||
|
this._emoteIdx = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
for (var i = 0; i < this.Emotes.Count; i++) {
|
||||||
|
var emote = this.Emotes[i];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unlocked) {
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.ClearIfNecessary();
|
this.ClearIfNecessary();
|
||||||
|
|
||||||
var valid = this.ValidSetup();
|
var valid = this.ValidSetup();
|
||||||
@ -310,7 +354,7 @@ internal class Write : ITab {
|
|||||||
var location = HousingLocation.Current();
|
var location = HousingLocation.Current();
|
||||||
var req = new MessageRequest {
|
var req = new MessageRequest {
|
||||||
Territory = this.Plugin.ClientState.TerritoryType,
|
Territory = this.Plugin.ClientState.TerritoryType,
|
||||||
World = this.Plugin.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0,
|
World = player.CurrentWorld.Id,
|
||||||
Ward = location?.Ward,
|
Ward = location?.Ward,
|
||||||
Plot = location?.CombinedPlot(),
|
Plot = location?.CombinedPlot(),
|
||||||
X = player.Position.X,
|
X = player.Position.X,
|
||||||
@ -326,6 +370,9 @@ internal class Write : ITab {
|
|||||||
Word2List = this._word2.Item1 == -1 ? null : this._word2.Item1,
|
Word2List = this._word2.Item1 == -1 ? null : this._word2.Item1,
|
||||||
Word2Word = this._word2.Item2 == -1 ? null : this._word2.Item2,
|
Word2Word = this._word2.Item2 == -1 ? null : this._word2.Item2,
|
||||||
Glyph = this._glyph,
|
Glyph = this._glyph,
|
||||||
|
Emote = this._emoteIdx == -1
|
||||||
|
? null
|
||||||
|
: this.GetEmoteData(this.Emotes[this._emoteIdx], player),
|
||||||
};
|
};
|
||||||
|
|
||||||
var json = JsonConvert.SerializeObject(req);
|
var json = JsonConvert.SerializeObject(req);
|
||||||
@ -348,7 +395,9 @@ internal class Write : ITab {
|
|||||||
Text = actualText,
|
Text = actualText,
|
||||||
NegativeVotes = 0,
|
NegativeVotes = 0,
|
||||||
PositiveVotes = 0,
|
PositiveVotes = 0,
|
||||||
|
UserVote = 0,
|
||||||
Glyph = this._glyph,
|
Glyph = this._glyph,
|
||||||
|
Emote = req.Emote,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Plugin.Messages.Add(newMsg);
|
this.Plugin.Messages.Add(newMsg);
|
||||||
@ -366,6 +415,50 @@ internal class Write : ITab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe EmoteData GetEmoteData(Emote emote, IPlayerCharacter player) {
|
||||||
|
var objMan = ClientObjectManager.Instance();
|
||||||
|
var chara = (BattleChara*) objMan->GetObjectByIndex(player.ObjectIndex);
|
||||||
|
|
||||||
|
return new EmoteData {
|
||||||
|
Id = emote.RowId,
|
||||||
|
Customise = player.Customize,
|
||||||
|
Equipment = chara->DrawData.EquipmentModelIds
|
||||||
|
.ToArray()
|
||||||
|
.Select(equip => new EquipmentData {
|
||||||
|
Id = equip.Id,
|
||||||
|
Variant = equip.Variant,
|
||||||
|
Stain0 = equip.Stain0,
|
||||||
|
Stain1 = equip.Stain1,
|
||||||
|
Value = equip.Value,
|
||||||
|
})
|
||||||
|
.ToArray(),
|
||||||
|
Weapon = chara->DrawData.WeaponData
|
||||||
|
.ToArray()
|
||||||
|
.Select(weapon => new WeaponData {
|
||||||
|
ModelId = new WeaponModelId {
|
||||||
|
Id = weapon.ModelId.Id,
|
||||||
|
Kind = weapon.ModelId.Type,
|
||||||
|
Variant = weapon.ModelId.Variant,
|
||||||
|
Stain0 = weapon.ModelId.Stain0,
|
||||||
|
Stain1 = weapon.ModelId.Stain1,
|
||||||
|
Value = weapon.ModelId.Value,
|
||||||
|
},
|
||||||
|
Flags1 = weapon.Flags1,
|
||||||
|
Flags2 = weapon.Flags2,
|
||||||
|
State = weapon.State,
|
||||||
|
})
|
||||||
|
.ToArray(),
|
||||||
|
Glasses = chara->DrawData.GlassesIds[0],
|
||||||
|
HatHidden = chara->DrawData.IsHatHidden,
|
||||||
|
VisorToggled = chara->DrawData.IsVisorToggled,
|
||||||
|
WeaponHidden = chara->DrawData.IsWeaponHidden,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool IsEmoteUnlocked(Emote emote) {
|
||||||
|
return UIState.Instance()->IsEmoteUnlocked((ushort) emote.RowId);
|
||||||
|
}
|
||||||
|
|
||||||
private void ResetWriter() {
|
private void ResetWriter() {
|
||||||
this._part1 = this._part2 = this._conj = -1;
|
this._part1 = this._part2 = this._conj = -1;
|
||||||
this._word1 = (-1, -1);
|
this._word1 = (-1, -1);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
namespace OrangeGuidanceTomestone.Util;
|
namespace OrangeGuidanceTomestone.Util;
|
||||||
|
|
||||||
@ -51,7 +52,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 != null) {
|
if (message?.Emote != null) {
|
||||||
this.Spawn(message);
|
this.Spawn(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,6 +109,11 @@ internal class ActorManager : IDisposable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.Emote == null) {
|
||||||
|
Plugin.Log.Warning("refusing to spawn an actor for a message without an emote");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var idx = objMan->CreateBattleCharacter();
|
var idx = objMan->CreateBattleCharacter();
|
||||||
if (idx == 0xFFFFFFFF) {
|
if (idx == 0xFFFFFFFF) {
|
||||||
Plugin.Log.Debug("actor could not be spawned");
|
Plugin.Log.Debug("actor could not be spawned");
|
||||||
@ -115,6 +121,7 @@ internal class ActorManager : IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
manager._idx = idx;
|
manager._idx = idx;
|
||||||
|
var emote = message.Emote;
|
||||||
|
|
||||||
var chara = (BattleChara*) objMan->GetObjectByIndex((ushort) idx);
|
var chara = (BattleChara*) objMan->GetObjectByIndex((ushort) idx);
|
||||||
|
|
||||||
@ -123,11 +130,53 @@ internal class ActorManager : IDisposable {
|
|||||||
chara->Position = message.Position;
|
chara->Position = message.Position;
|
||||||
chara->Rotation = message.Yaw;
|
chara->Rotation = message.Yaw;
|
||||||
var drawData = &chara->DrawData;
|
var drawData = &chara->DrawData;
|
||||||
drawData->CustomizeData = new CustomizeData();
|
|
||||||
|
var maxLen = Math.Min(sizeof(CustomizeData), emote.Customise.Length);
|
||||||
|
var rawCustomise = (byte*) &drawData->CustomizeData;
|
||||||
|
for (var i = 0; i < maxLen; i++) {
|
||||||
|
rawCustomise[i] = emote.Customise[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < Math.Min(drawData->EquipmentModelIds.Length, emote.Equipment.Length); i++) {
|
||||||
|
var equip = emote.Equipment[i];
|
||||||
|
drawData->Equipment((DrawDataContainer.EquipmentSlot) i) = new EquipmentModelId {
|
||||||
|
Id = equip.Id,
|
||||||
|
Variant = equip.Variant,
|
||||||
|
Stain0 = equip.Stain0,
|
||||||
|
Stain1 = equip.Stain1,
|
||||||
|
Value = equip.Value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < Math.Min(drawData->WeaponData.Length, emote.Weapon.Length); i++) {
|
||||||
|
var weapon = emote.Weapon[i];
|
||||||
|
drawData->Weapon((DrawDataContainer.WeaponSlot) i) = new DrawObjectData {
|
||||||
|
ModelId = new FFXIVClientStructs.FFXIV.Client.Game.Character.WeaponModelId {
|
||||||
|
Id = weapon.ModelId.Id,
|
||||||
|
Type = weapon.ModelId.Kind,
|
||||||
|
Variant = weapon.ModelId.Variant,
|
||||||
|
Stain0 = weapon.ModelId.Stain0,
|
||||||
|
Stain1 = weapon.ModelId.Stain1,
|
||||||
|
Value = weapon.ModelId.Value,
|
||||||
|
},
|
||||||
|
DrawObject = chara->DrawObject,
|
||||||
|
Flags1 = weapon.Flags1,
|
||||||
|
Flags2 = weapon.Flags2,
|
||||||
|
State = weapon.State,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
drawData->IsHatHidden = emote.HatHidden;
|
||||||
|
drawData->IsVisorToggled = emote.VisorToggled;
|
||||||
|
drawData->IsWeaponHidden = emote.WeaponHidden;
|
||||||
|
|
||||||
|
drawData->SetGlasses(0, (ushort) emote.Glasses);
|
||||||
|
|
||||||
chara->Alpha = 0.25f;
|
chara->Alpha = 0.25f;
|
||||||
chara->SetMode(CharacterModes.AnimLock, 0);
|
chara->SetMode(CharacterModes.AnimLock, 0);
|
||||||
chara->Timeline.BaseOverride = 4818;
|
if (manager.Plugin.DataManager.GetExcelSheet<Emote>()?.GetRow(emote.Id) is { } row) {
|
||||||
|
chara->Timeline.BaseOverride = (ushort) row.ActionTimeline[0].Row;
|
||||||
|
}
|
||||||
|
|
||||||
manager._tasks.Enqueue(new EnableAction());
|
manager._tasks.Enqueue(new EnableAction());
|
||||||
return true;
|
return true;
|
||||||
|
1
server/Cargo.lock
generated
1
server/Cargo.lock
generated
@ -1322,6 +1322,7 @@ dependencies = [
|
|||||||
"rand",
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"sha3",
|
"sha3",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
@ -15,6 +15,7 @@ parking_lot = "0.12"
|
|||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
sha3 = "0.10"
|
sha3 = "0.10"
|
||||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "chrono"] }
|
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "chrono"] }
|
||||||
|
2
server/migrations/17_add_emotes.sql
Normal file
2
server/migrations/17_add_emotes.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
alter table messages
|
||||||
|
add column emote text default null;
|
@ -1,5 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::types::chrono::NaiveDateTime;
|
use sqlx::types::{chrono::NaiveDateTime, Json};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -29,6 +29,9 @@ pub struct Message {
|
|||||||
pub ward: Option<u16>,
|
pub ward: Option<u16>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub plot: Option<u16>,
|
pub plot: Option<u16>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub emote: Option<EmoteData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glyph_default() -> i8 {
|
fn glyph_default() -> i8 {
|
||||||
@ -47,6 +50,7 @@ pub struct RetrievedMessage {
|
|||||||
pub negative_votes: i32,
|
pub negative_votes: i32,
|
||||||
pub user_vote: i64,
|
pub user_vote: i64,
|
||||||
pub glyph: i64,
|
pub glyph: i64,
|
||||||
|
pub emote: Option<Json<Option<EmoteData>>>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub created: NaiveDateTime,
|
pub created: NaiveDateTime,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
@ -71,6 +75,7 @@ pub struct RetrievedMessageTerritory {
|
|||||||
pub negative_votes: i32,
|
pub negative_votes: i32,
|
||||||
pub user_vote: i64,
|
pub user_vote: i64,
|
||||||
pub glyph: i64,
|
pub glyph: i64,
|
||||||
|
pub emote: Option<Json<Option<EmoteData>>>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub created: NaiveDateTime,
|
pub created: NaiveDateTime,
|
||||||
}
|
}
|
||||||
@ -91,7 +96,47 @@ pub struct OwnMessage {
|
|||||||
pub negative_votes: i32,
|
pub negative_votes: i32,
|
||||||
pub user_vote: i64,
|
pub user_vote: i64,
|
||||||
pub glyph: i64,
|
pub glyph: i64,
|
||||||
|
pub emote: Option<Json<Option<EmoteData>>>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub created: NaiveDateTime,
|
pub created: NaiveDateTime,
|
||||||
pub is_hidden: bool,
|
pub is_hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct EmoteData {
|
||||||
|
pub id: u32,
|
||||||
|
pub customise: Vec<u8>,
|
||||||
|
pub equipment_data: Vec<EquipmentData>,
|
||||||
|
pub weapon_data: Vec<WeaponData>,
|
||||||
|
pub glasses: u32,
|
||||||
|
pub hat_hidden: bool,
|
||||||
|
pub visor_toggled: bool,
|
||||||
|
pub weapon_hidden: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct EquipmentData {
|
||||||
|
pub id: u16,
|
||||||
|
pub variant: u8,
|
||||||
|
pub stain_0: u8,
|
||||||
|
pub stain_1: u8,
|
||||||
|
pub value: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct WeaponData {
|
||||||
|
pub model_id: WeaponModelId,
|
||||||
|
pub state: u8,
|
||||||
|
pub flags_1: u16,
|
||||||
|
pub flags_2: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct WeaponModelId {
|
||||||
|
pub id: u16,
|
||||||
|
pub kind: u16,
|
||||||
|
pub variant: u16,
|
||||||
|
pub stain_0: u8,
|
||||||
|
pub stain_1: u8,
|
||||||
|
pub value: u64,
|
||||||
|
}
|
||||||
|
@ -7,10 +7,11 @@ use rand::Rng;
|
|||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use sqlx::types::Json;
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
use warp::filters::BoxedFilter;
|
use warp::filters::BoxedFilter;
|
||||||
|
|
||||||
use crate::message::RetrievedMessage;
|
use crate::message::{EmoteData, RetrievedMessage};
|
||||||
use crate::State;
|
use crate::State;
|
||||||
use crate::util::HOUSING_ZONES;
|
use crate::util::HOUSING_ZONES;
|
||||||
use crate::web::{AnyhowRejection, WebError};
|
use crate::web::{AnyhowRejection, WebError};
|
||||||
@ -68,6 +69,7 @@ async fn logic(state: Arc<State>, id: i64, location: u32, query: GetLocationQuer
|
|||||||
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
|
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
|
||||||
coalesce(v2.vote, 0) as user_vote,
|
coalesce(v2.vote, 0) as user_vote,
|
||||||
m.glyph,
|
m.glyph,
|
||||||
|
m.emote as "emote: Json<Option<EmoteData>>",
|
||||||
m.created,
|
m.created,
|
||||||
m.user,
|
m.user,
|
||||||
coalesce(cast((julianday(current_timestamp) - julianday(u.last_seen)) * 1440 as int), 0) as last_seen_minutes
|
coalesce(cast((julianday(current_timestamp) - julianday(u.last_seen)) * 1440 as int), 0) as last_seen_minutes
|
||||||
@ -103,6 +105,7 @@ async fn logic(state: Arc<State>, id: i64, location: u32, query: GetLocationQuer
|
|||||||
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
|
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
|
||||||
coalesce(v2.vote, 0) as user_vote,
|
coalesce(v2.vote, 0) as user_vote,
|
||||||
m.glyph,
|
m.glyph,
|
||||||
|
m.emote as "emote: Json<Option<EmoteData>>",
|
||||||
m.created,
|
m.created,
|
||||||
m.user,
|
m.user,
|
||||||
coalesce(cast((julianday(current_timestamp) - julianday(u.last_seen)) * 1440 as int), 0) as last_seen_minutes
|
coalesce(cast((julianday(current_timestamp) - julianday(u.last_seen)) * 1440 as int), 0) as last_seen_minutes
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use sqlx::types::Json;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
use warp::filters::BoxedFilter;
|
use warp::filters::BoxedFilter;
|
||||||
|
|
||||||
use crate::message::RetrievedMessageTerritory;
|
use crate::message::{EmoteData, RetrievedMessageTerritory};
|
||||||
use crate::State;
|
use crate::State;
|
||||||
use crate::web::{AnyhowRejection, WebError};
|
use crate::web::{AnyhowRejection, WebError};
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ async fn logic(state: Arc<State>, id: i64, message_id: Uuid) -> Result<impl Repl
|
|||||||
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
|
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
|
||||||
coalesce(v2.vote, 0) as user_vote,
|
coalesce(v2.vote, 0) as user_vote,
|
||||||
m.glyph,
|
m.glyph,
|
||||||
|
m.emote as "emote: Json<Option<EmoteData>>",
|
||||||
m.created
|
m.created
|
||||||
from messages m
|
from messages m
|
||||||
left join votes v on m.id = v.message
|
left join votes v on m.id = v.message
|
||||||
|
@ -2,10 +2,11 @@ use std::collections::HashMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use sqlx::types::Json;
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
use warp::filters::BoxedFilter;
|
use warp::filters::BoxedFilter;
|
||||||
|
|
||||||
use crate::message::OwnMessage;
|
use crate::message::{EmoteData, OwnMessage};
|
||||||
use crate::State;
|
use crate::State;
|
||||||
use crate::web::AnyhowRejection;
|
use crate::web::AnyhowRejection;
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ async fn logic(state: Arc<State>, id: i64, extra: i64, mut query: HashMap<String
|
|||||||
coalesce(v2.vote, 0) as user_vote,
|
coalesce(v2.vote, 0) as user_vote,
|
||||||
m.glyph,
|
m.glyph,
|
||||||
m.created,
|
m.created,
|
||||||
|
m.emote as "emote: Json<Option<EmoteData>>",
|
||||||
0 as "is_hidden: bool"
|
0 as "is_hidden: bool"
|
||||||
from messages m
|
from messages m
|
||||||
left join votes v on m.id = v.message
|
left join votes v on m.id = v.message
|
||||||
|
@ -65,9 +65,13 @@ async fn logic(state: Arc<State>, id: i64, extra: i64, message: Message) -> Resu
|
|||||||
let message_id = Uuid::new_v4().simple().to_string();
|
let message_id = Uuid::new_v4().simple().to_string();
|
||||||
let territory = message.territory as i64;
|
let territory = message.territory as i64;
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&message.emote)
|
||||||
|
.context("could not serialise emote")
|
||||||
|
.map_err(AnyhowRejection)
|
||||||
|
.map_err(warp::reject::custom)?;
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
// language=sqlite
|
// language=sqlite
|
||||||
"insert into messages (id, user, territory, world, ward, plot, x, y, z, yaw, message, glyph) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
"insert into messages (id, user, territory, world, ward, plot, x, y, z, yaw, message, glyph, emote) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
message_id,
|
message_id,
|
||||||
id,
|
id,
|
||||||
territory,
|
territory,
|
||||||
@ -80,6 +84,7 @@ async fn logic(state: Arc<State>, id: i64, extra: i64, message: Message) -> Resu
|
|||||||
message.yaw,
|
message.yaw,
|
||||||
text,
|
text,
|
||||||
message.glyph,
|
message.glyph,
|
||||||
|
json,
|
||||||
)
|
)
|
||||||
.execute(&state.db)
|
.execute(&state.db)
|
||||||
.await
|
.await
|
||||||
|
Loading…
Reference in New Issue
Block a user