feat: add chat database

This commit is contained in:
Anna 2022-02-13 04:36:08 -05:00
parent 18c311ace5
commit 9b7b84a58d
25 changed files with 476 additions and 95 deletions

View File

@ -48,6 +48,7 @@
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.5"/>
<PackageReference Include="LiteDB" Version="5.0.11"/>
<PackageReference Include="SharpDX.Direct2D1" Version="4.2.0"/>
<PackageReference Include="XivCommon" Version="5.0.0"/>
</ItemGroup>

View File

@ -1,17 +1,33 @@
using ChatTwo.Code;
using Dalamud.Game.Text.SeStringHandling;
using LiteDB;
namespace ChatTwo;
internal abstract class Chunk {
[BsonIgnore]
internal Message? Message { get; set; }
internal SeString? Source { get; set; }
internal ChunkSource Source { get; set; }
internal Payload? Link { get; set; }
protected Chunk(SeString? source, Payload? link) {
protected Chunk(ChunkSource source, Payload? link) {
this.Source = source;
this.Link = link;
}
internal SeString? GetSeString() => this.Source switch {
ChunkSource.None => null,
ChunkSource.Sender => this.Message?.SenderSource,
ChunkSource.Content => this.Message?.ContentSource,
_ => null,
};
}
internal enum ChunkSource {
None,
Sender,
Content,
}
internal class TextChunk : Chunk {
@ -21,23 +37,23 @@ internal class TextChunk : Chunk {
internal bool Italic { get; set; }
internal string Content { get; set; }
internal TextChunk(SeString? source, Payload? link, string content) : base(source, link) {
internal TextChunk(ChunkSource source, Payload? link, string content) : base(source, link) {
this.Content = content;
}
internal TextChunk(SeString? source, Payload? link, ChatType? fallbackColour, uint? foreground, uint? glow, bool italic, string content) : base(source, link) {
this.FallbackColour = fallbackColour;
this.Foreground = foreground;
this.Glow = glow;
this.Italic = italic;
this.Content = content;
#pragma warning disable CS8618
public TextChunk() : base(ChunkSource.None, null) {
}
#pragma warning restore CS8618
}
internal class IconChunk : Chunk {
internal BitmapFontIcon Icon { get; set; }
public IconChunk(SeString? source, Payload? link, BitmapFontIcon icon) : base(source, link) {
public IconChunk(ChunkSource source, Payload? link, BitmapFontIcon icon) : base(source, link) {
this.Icon = icon;
}
public IconChunk() : base(ChunkSource.None, null) {
}
}

View File

@ -1,17 +1,30 @@
namespace ChatTwo.Code;
using LiteDB;
namespace ChatTwo.Code;
internal class ChatCode {
private const ushort Clear7 = ~(~0 << 7);
internal ushort Raw { get; }
internal ChatType Type => (ChatType) (this.Raw & Clear7);
internal ChatSource Source => this.SourceFrom(11);
internal ChatSource Target => this.SourceFrom(7);
internal ChatType Type { get; }
internal ChatSource Source { get; }
internal ChatSource Target { get; }
private ChatSource SourceFrom(ushort shift) => (ChatSource) (1 << ((this.Raw >> shift) & 0xF));
internal ChatCode(ushort raw) {
this.Raw = raw;
this.Type = (ChatType) (this.Raw & Clear7);
this.Source = this.SourceFrom(11);
this.Target = this.SourceFrom(7);
}
[BsonCtor]
public ChatCode(ushort raw, ChatType type, ChatSource source, ChatSource target) {
this.Raw = raw;
this.Type = type;
this.Source = source;
this.Target = target;
}
internal ChatType Parent() => this.Type switch {

View File

@ -345,10 +345,16 @@ internal static class ChatTypeExt {
ChatType.LoseDebuff => true,
// Announcements
ChatType.System => true,
ChatType.BattleSystem => true,
ChatType.Error => true,
ChatType.LootNotice => true,
ChatType.Progress => true,
ChatType.LootRoll => true,
ChatType.Crafting => true,
ChatType.Gathering => true,
ChatType.FreeCompanyLoginLogout => true,
ChatType.PvpTeamLoginLogout => true,
_ => false,
};
}

View File

@ -27,6 +27,9 @@ internal class Configuration : IPluginConfiguration {
public bool CanMove = true;
public bool CanResize = true;
public bool ShowTitleBar;
public bool DatabaseBattleMessages;
public bool LoadPreviousSession;
public bool FilterIncludePreviousSessions;
public float FontSize = 17f;
public float JapaneseFontSize = 17f;
@ -54,6 +57,9 @@ internal class Configuration : IPluginConfiguration {
this.CanMove = other.CanMove;
this.CanResize = other.CanResize;
this.ShowTitleBar = other.ShowTitleBar;
this.DatabaseBattleMessages = other.DatabaseBattleMessages;
this.LoadPreviousSession = other.LoadPreviousSession;
this.FilterIncludePreviousSessions = other.FilterIncludePreviousSessions;
this.FontSize = other.FontSize;
this.JapaneseFontSize = other.JapaneseFontSize;
this.SymbolsFontSize = other.SymbolsFontSize;

View File

@ -503,7 +503,7 @@ internal sealed unsafe class Chat : IDisposable {
return ret;
}
var nameChunks = ChunkUtil.ToChunks(name, null).ToList();
var nameChunks = ChunkUtil.ToChunks(name, ChunkSource.None, null).ToList();
if (nameChunks.Count > 0 && nameChunks[0] is TextChunk text) {
text.Content = text.Content.TrimStart('\uE01E').TrimStart();
}

View File

@ -1,10 +1,32 @@
using ChatTwo.Code;
using Dalamud.Game.Text.SeStringHandling;
using LiteDB;
namespace ChatTwo;
internal class SortCode {
internal ChatType Type { get; set; }
internal ChatSource Source { get; set; }
internal SortCode(ChatType type, ChatSource source) {
this.Type = type;
this.Source = source;
}
public SortCode() {
}
}
internal class Message {
internal ulong ContentId;
// ReSharper disable once UnusedMember.Global
internal ObjectId Id { get; } = ObjectId.NewObjectId();
internal ulong Receiver { get; }
internal ulong ContentId { get; set; }
[BsonIgnore]
internal float? Height;
[BsonIgnore]
internal bool IsVisible;
internal DateTime Date { get; }
@ -12,14 +34,40 @@ internal class Message {
internal List<Chunk> Sender { get; }
internal List<Chunk> Content { get; }
internal Message(ChatCode code, List<Chunk> sender, List<Chunk> content) {
internal SeString SenderSource { get; }
internal SeString ContentSource { get; }
internal SortCode SortCode { get; }
internal Message(ulong receiver, ChatCode code, List<Chunk> sender, List<Chunk> content, SeString senderSource, SeString contentSource) {
this.Receiver = receiver;
this.Date = DateTime.UtcNow;
this.Code = code;
this.Sender = sender;
this.Content = content;
this.SenderSource = senderSource;
this.ContentSource = contentSource;
this.SortCode = new SortCode(this.Code.Type, this.Code.Source);
foreach (var chunk in sender.Concat(content)) {
chunk.Message = this;
}
}
internal Message(ObjectId id, ulong receiver, ulong contentId, DateTime date, BsonDocument code, BsonArray sender, BsonArray content, BsonDocument senderSource, BsonDocument contentSource, BsonDocument sortCode) {
this.Id = id;
this.Receiver = receiver;
this.ContentId = contentId;
this.Date = date;
this.Code = BsonMapper.Global.ToObject<ChatCode>(code);
this.Sender = BsonMapper.Global.Deserialize<List<Chunk>>(sender);
this.Content = BsonMapper.Global.Deserialize<List<Chunk>>(content);
this.SenderSource = BsonMapper.Global.ToObject<SeString>(senderSource);
this.ContentSource = BsonMapper.Global.ToObject<SeString>(contentSource);
this.SortCode = BsonMapper.Global.ToObject<SortCode>(sortCode);
foreach (var chunk in this.Sender.Concat(this.Content)) {
chunk.Message = this;
}
}
}

View File

@ -195,11 +195,11 @@ internal sealed class PayloadHandler {
InlineIcon(icon);
}
var name = ChunkUtil.ToChunks(status.Status.Name.ToDalamudString(), null);
var name = ChunkUtil.ToChunks(status.Status.Name.ToDalamudString(), ChunkSource.None, null);
this.Log.DrawChunks(name.ToList());
ImGui.Separator();
var desc = ChunkUtil.ToChunks(status.Status.Description.ToDalamudString(), null);
var desc = ChunkUtil.ToChunks(status.Status.Description.ToDalamudString(), ChunkSource.None, null);
this.Log.DrawChunks(desc.ToList());
}
@ -217,11 +217,11 @@ internal sealed class PayloadHandler {
InlineIcon(icon);
}
var name = ChunkUtil.ToChunks(item.Item.Name.ToDalamudString(), null);
var name = ChunkUtil.ToChunks(item.Item.Name.ToDalamudString(), ChunkSource.None, null);
this.Log.DrawChunks(name.ToList());
ImGui.Separator();
var desc = ChunkUtil.ToChunks(item.Item.Description.ToDalamudString(), null);
var desc = ChunkUtil.ToChunks(item.Item.Description.ToDalamudString(), ChunkSource.None, null);
this.Log.DrawChunks(desc.ToList());
}
@ -235,13 +235,13 @@ internal sealed class PayloadHandler {
InlineIcon(icon);
}
var name = ChunkUtil.ToChunks(item.Name.ToDalamudString(), null);
var name = ChunkUtil.ToChunks(item.Name.ToDalamudString(), ChunkSource.None, null);
this.Log.DrawChunks(name.ToList());
ImGui.Separator();
var help = this.Ui.Plugin.DataManager.GetExcelSheet<EventItemHelp>()?.GetRow(payload.RawItemId);
if (help != null) {
var desc = ChunkUtil.ToChunks(help.Description.ToDalamudString(), null);
var desc = ChunkUtil.ToChunks(help.Description.ToDalamudString(), ChunkSource.None, null);
this.Log.DrawChunks(desc.ToList());
}
}
@ -279,7 +279,7 @@ internal sealed class PayloadHandler {
}
private void ClickLinkPayload(Chunk chunk, Payload payload, DalamudLinkPayload link) {
if (chunk.Source is not { } source) {
if (chunk.GetSeString() is not { } source) {
return;
}
@ -334,7 +334,7 @@ internal sealed class PayloadHandler {
name.Payloads.Add(new TextPayload(" "));
}
this.Log.DrawChunks(ChunkUtil.ToChunks(name, null).ToList(), false);
this.Log.DrawChunks(ChunkUtil.ToChunks(name, ChunkSource.None, null).ToList(), false);
ImGui.Separator();
var realItemId = payload.RawItemId;
@ -383,7 +383,7 @@ internal sealed class PayloadHandler {
}
var name = item.Name.ToDalamudString();
this.Log.DrawChunks(ChunkUtil.ToChunks(name, null).ToList(), false);
this.Log.DrawChunks(ChunkUtil.ToChunks(name, ChunkSource.None, null).ToList(), false);
ImGui.Separator();
var realItemId = payload.RawItemId;
@ -398,11 +398,11 @@ internal sealed class PayloadHandler {
}
private void DrawPlayerPopup(Chunk chunk, PlayerPayload player) {
var name = new List<Chunk> { new TextChunk(null, null, player.PlayerName) };
var name = new List<Chunk> { new TextChunk(ChunkSource.None, null, player.PlayerName) };
if (player.World.IsPublic) {
name.AddRange(new Chunk[] {
new IconChunk(null, null, BitmapFontIcon.CrossWorld),
new TextChunk(null, null, player.World.Name),
new IconChunk(ChunkSource.None, null, BitmapFontIcon.CrossWorld),
new TextChunk(ChunkSource.None, null, player.World.Name),
});
}

View File

@ -1,4 +1,5 @@
using System.Globalization;
using System.Diagnostics;
using System.Globalization;
using ChatTwo.Resources;
using Dalamud.Data;
using Dalamud.Game;
@ -66,8 +67,12 @@ public sealed class Plugin : IDalamudPlugin {
internal int DeferredSaveFrames = -1;
internal DateTime GameStarted { get; }
#pragma warning disable CS8618
public Plugin() {
this.GameStarted = Process.GetCurrentProcess().StartTime.ToUniversalTime();
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
this.Config.Migrate();
@ -79,6 +84,10 @@ public sealed class Plugin : IDalamudPlugin {
this.Store = new Store(this);
this.Ui = new PluginUi(this);
if (this.Interface.Reason is not PluginLoadReason.Boot) {
this.Store.FilterAllTabs(false);
}
this.Framework!.Update += this.FrameworkUpdate;
this.Interface.LanguageChanged += this.LanguageChanged;
}

View File

@ -132,10 +132,26 @@ internal sealed class PluginUi : IDisposable {
component.Dispose();
}
this._regularFont.Item1.Free();
this._italicFont.Item1.Free();
this._gameSymFont.Item1.Free();
this._symRange.Free();
if (this._regularFont.Item1.IsAllocated) {
this._regularFont.Item1.Free();
}
if (this._italicFont.Item1.IsAllocated) {
this._italicFont.Item1.Free();
}
if (this._jpFont.Item1.IsAllocated) {
this._jpFont.Item1.Free();
}
if (this._gameSymFont.Item1.IsAllocated) {
this._gameSymFont.Item1.Free();
}
if (this._symRange.IsAllocated) {
this._symRange.Free();
}
this._fontCfg.Destroy();
this._fontCfgMerge.Destroy();
}

View File

@ -402,6 +402,33 @@ namespace ChatTwo.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Database.
/// </summary>
internal static string Options_Database_Tab {
get {
return ResourceManager.GetString("Options_Database_Tab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to If battle messages are saved to the database, the size of the database will grow much faster, and there will be a noticeable freeze when saving settings. It is recommended to leave this disabled..
/// </summary>
internal static string Options_DatabaseBattleMessages_Description {
get {
return ResourceManager.GetString("Options_DatabaseBattleMessages_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save battle messages in database.
/// </summary>
internal static string Options_DatabaseBattleMessages_Name {
get {
return ResourceManager.GetString("Options_DatabaseBattleMessages_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Display.
/// </summary>
@ -411,6 +438,24 @@ namespace ChatTwo.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Include messages from before the game was launched when populating tabs. Tabs are populated when the settings are saved..
/// </summary>
internal static string Options_FilterIncludePreviousSessions_Description {
get {
return ResourceManager.GetString("Options_FilterIncludePreviousSessions_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Include previous sessions when populating tabs.
/// </summary>
internal static string Options_FilterIncludePreviousSessions_Name {
get {
return ResourceManager.GetString("Options_FilterIncludePreviousSessions_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The font {0} will use to display non-Japanese text..
/// </summary>
@ -591,6 +636,24 @@ namespace ChatTwo.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to After logging in, populate the chat with the messages from when you last logged off..
/// </summary>
internal static string Options_LoadPreviousSession_Description {
get {
return ResourceManager.GetString("Options_LoadPreviousSession_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Display previous chat session on login.
/// </summary>
internal static string Options_LoadPreviousSession_Name {
get {
return ResourceManager.GetString("Options_LoadPreviousSession_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Miscellaneous.
/// </summary>

View File

@ -437,4 +437,25 @@
<data name="Options_Language_Description" xml:space="preserve">
<value>The language to display {0} in.</value>
</data>
<data name="Options_DatabaseBattleMessages_Name" xml:space="preserve">
<value>Save battle messages in database</value>
</data>
<data name="Options_DatabaseBattleMessages_Description" xml:space="preserve">
<value>If battle messages are saved to the database, the size of the database will grow much faster, and there will be a noticeable freeze when saving settings. It is recommended to leave this disabled.</value>
</data>
<data name="Options_LoadPreviousSession_Name" xml:space="preserve">
<value>Display previous chat session on login</value>
</data>
<data name="Options_LoadPreviousSession_Description" xml:space="preserve">
<value>After logging in, populate the chat with the messages from when you last logged off.</value>
</data>
<data name="Options_Database_Tab" xml:space="preserve">
<value>Database</value>
</data>
<data name="Options_FilterIncludePreviousSessions_Name" xml:space="preserve">
<value>Include previous sessions when populating tabs</value>
</data>
<data name="Options_FilterIncludePreviousSessions_Description" xml:space="preserve">
<value>Include messages from before the game was launched when populating tabs. Tabs are populated when the settings are saved.</value>
</data>
</root>

View File

@ -1,9 +1,11 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using ChatTwo.Code;
using ChatTwo.Util;
using Dalamud.Game;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using LiteDB;
using Lumina.Excel.GeneratedSheets;
namespace ChatTwo;
@ -11,63 +13,146 @@ namespace ChatTwo;
internal class Store : IDisposable {
internal const int MessagesLimit = 10_000;
internal sealed class MessagesLock : IDisposable {
private Mutex Mutex { get; }
internal List<Message> Messages { get; }
internal MessagesLock(List<Message> messages, Mutex mutex) {
this.Messages = messages;
this.Mutex = mutex;
this.Mutex.WaitOne();
}
public void Dispose() {
this.Mutex.ReleaseMutex();
}
}
private Plugin Plugin { get; }
private Mutex MessagesMutex { get; } = new();
private List<Message> Messages { get; } = new();
private ConcurrentQueue<(uint, Message)> Pending { get; } = new();
private Stopwatch CheckpointTimer { get; } = new();
internal ILiteDatabase Database { get; }
private ILiteCollection<Message> Messages => this.Database.GetCollection<Message>("messages");
private Dictionary<ChatType, NameFormatting> Formats { get; } = new();
private ulong LastContentId { get; set; }
private ulong CurrentContentId {
get {
var contentId = this.Plugin.ClientState.LocalContentId;
return contentId == 0 ? this.LastContentId : contentId;
}
}
internal Store(Plugin plugin) {
this.Plugin = plugin;
this.CheckpointTimer.Start();
var dir = this.Plugin.Interface.ConfigDirectory;
dir.Create();
BsonMapper.Global = new BsonMapper {
IncludeNonPublic = true,
TrimWhitespace = false,
// EnumAsInteger = true,
};
BsonMapper.Global.Entity<Message>()
.Id(msg => msg.Id)
.Ctor(doc => new Message(
doc["_id"].AsObjectId,
(ulong) doc["Receiver"].AsInt64,
(ulong) doc["ContentId"].AsInt64,
DateTime.UnixEpoch.AddMilliseconds(doc["Date"].AsInt64),
doc["Code"].AsDocument,
doc["Sender"].AsArray,
doc["Content"].AsArray,
doc["SenderSource"].AsDocument,
doc["ContentSource"].AsDocument,
doc["SortCode"].AsDocument
));
BsonMapper.Global.RegisterType<Payload?>(
payload => {
switch (payload) {
case AchievementPayload achievement:
return new BsonDocument(new Dictionary<string, BsonValue> {
["Type"] = new("Achievement"),
["Id"] = new(achievement.Id),
});
case PartyFinderPayload partyFinder:
return new BsonDocument(new Dictionary<string, BsonValue> {
["Type"] = new("PartyFinder"),
["Id"] = new(partyFinder.Id),
});
}
return payload?.Encode();
},
bson => {
if (bson.IsNull) {
return null;
}
if (bson.IsDocument) {
return bson["Type"].AsString switch {
"Achievement" => new AchievementPayload((uint) bson["Id"].AsInt64),
"PartyFinder" => new PartyFinderPayload((uint) bson["Id"].AsInt64),
_ => null,
};
}
return Payload.Decode(new BinaryReader(new MemoryStream(bson.AsBinary)));
});
BsonMapper.Global.RegisterType(
type => (int) type,
bson => (ChatType) bson.AsInt32
);
BsonMapper.Global.RegisterType(
source => (int) source,
bson => (ChatSource) bson.AsInt32
);
BsonMapper.Global.RegisterType(
dateTime => dateTime.Subtract(DateTime.UnixEpoch).TotalMilliseconds,
bson => DateTime.UnixEpoch.AddMilliseconds(bson.AsInt64)
);
this.Database = new LiteDatabase(Path.Join(dir.FullName, "chat.db"), BsonMapper.Global) {
CheckpointSize = 1_000,
Timeout = TimeSpan.FromSeconds(1),
};
this.Messages.EnsureIndex(msg => msg.Date);
this.Messages.EnsureIndex(msg => msg.SortCode);
this.Plugin.ChatGui.ChatMessageUnhandled += this.ChatMessage;
this.Plugin.Framework.Update += this.GetMessageInfo;
this.Plugin.Framework.Update += this.UpdateReceiver;
this.Plugin.ClientState.Logout += this.Logout;
}
public void Dispose() {
this.Plugin.ClientState.Logout -= this.Logout;
this.Plugin.Framework.Update -= this.UpdateReceiver;
this.Plugin.Framework.Update -= this.GetMessageInfo;
this.Plugin.ChatGui.ChatMessageUnhandled -= this.ChatMessage;
this.MessagesMutex.Dispose();
this.Database.Dispose();
}
private void Logout(object? sender, EventArgs eventArgs) {
this.LastContentId = 0;
}
private void UpdateReceiver(Framework framework) {
var contentId = this.Plugin.ClientState.LocalContentId;
if (contentId != 0) {
this.LastContentId = contentId;
}
}
private void GetMessageInfo(Framework framework) {
if (this.CheckpointTimer.Elapsed > TimeSpan.FromMinutes(5)) {
this.CheckpointTimer.Restart();
new Thread(() => this.Database.Checkpoint()).Start();
}
if (!this.Pending.TryDequeue(out var entry)) {
return;
}
var contentId = this.Plugin.Functions.Chat.GetContentIdForEntry(entry.Item1);
entry.Item2.ContentId = contentId ?? 0;
}
internal MessagesLock GetMessages() {
return new MessagesLock(this.Messages, this.MessagesMutex);
if (this.Plugin.Config.DatabaseBattleMessages || !entry.Item2.Code.IsBattle()) {
this.Messages.Update(entry.Item2);
}
}
internal void AddMessage(Message message, Tab? currentTab) {
using var messages = this.GetMessages();
messages.Messages.Add(message);
while (messages.Messages.Count > MessagesLimit) {
messages.Messages.RemoveAt(0);
if (this.Plugin.Config.DatabaseBattleMessages || !message.Code.IsBattle()) {
this.Messages.Insert(message);
}
var currentMatches = currentTab?.Matches(message) ?? false;
@ -88,12 +173,36 @@ internal class Store : IDisposable {
}
internal void FilterTab(Tab tab, bool unread) {
using var messages = this.GetMessages();
foreach (var message in messages.Messages) {
if (tab.Matches(message)) {
tab.AddMessage(message, unread);
var sortCodes = new List<SortCode>();
foreach (var (type, sources) in tab.ChatCodes) {
sortCodes.Add(new SortCode(type, 0));
sortCodes.Add(new SortCode(type, (ChatSource) 1));
if (type.HasSource()) {
foreach (var source in Enum.GetValues<ChatSource>()) {
if (sources.HasFlag(source)) {
sortCodes.Add(new SortCode(type, source));
}
}
}
}
var query = this.Messages
.Query()
.OrderByDescending(msg => msg.Date)
.Where(msg => sortCodes.Contains(msg.SortCode))
.Where(msg => msg.Receiver == this.CurrentContentId);
if (!this.Plugin.Config.FilterIncludePreviousSessions) {
query = query.Where(msg => msg.Date >= this.Plugin.GameStarted);
}
var messages = query
.Limit(MessagesLimit)
.ToEnumerable()
.Reverse();
foreach (var message in messages) {
tab.AddMessage(message, unread);
}
}
private void ChatMessage(XivChatType type, uint senderId, SeString sender, SeString message) {
@ -106,18 +215,18 @@ internal class Store : IDisposable {
var senderChunks = new List<Chunk>();
if (formatting is { IsPresent: true }) {
senderChunks.Add(new TextChunk(null, null, formatting.Before) {
senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.Before) {
FallbackColour = chatCode.Type,
});
senderChunks.AddRange(ChunkUtil.ToChunks(sender, chatCode.Type));
senderChunks.Add(new TextChunk(null, null, formatting.After) {
senderChunks.AddRange(ChunkUtil.ToChunks(sender, ChunkSource.Sender, chatCode.Type));
senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.After) {
FallbackColour = chatCode.Type,
});
}
var messageChunks = ChunkUtil.ToChunks(message, chatCode.Type).ToList();
var messageChunks = ChunkUtil.ToChunks(message, ChunkSource.Content, chatCode.Type).ToList();
var msg = new Message(chatCode, senderChunks, messageChunks);
var msg = new Message(this.CurrentContentId, chatCode, senderChunks, messageChunks, sender, message);
this.AddMessage(msg, this.Plugin.Ui.CurrentTab);
var idx = this.Plugin.Functions.GetCurrentChatLogEntryIndex();

View File

@ -53,14 +53,28 @@ internal sealed class ChatLog : IUiComponent {
this._fontIcon = this.Ui.Plugin.DataManager.GetImGuiTexture("common/font/fonticon_ps5.tex");
this.Ui.Plugin.Functions.Chat.Activated += this.Activated;
this.Ui.Plugin.ClientState.Login += this.Login;
this.Ui.Plugin.ClientState.Logout += this.Logout;
}
public void Dispose() {
this.Ui.Plugin.ClientState.Logout -= this.Logout;
this.Ui.Plugin.ClientState.Login -= this.Login;
this.Ui.Plugin.Functions.Chat.Activated -= this.Activated;
this._fontIcon?.Dispose();
this.Ui.Plugin.CommandManager.RemoveHandler("/clearlog2");
}
private void Logout(object? sender, EventArgs e) {
foreach (var tab in this.Ui.Plugin.Config.Tabs) {
tab.Clear();
}
}
private void Login(object? sender, EventArgs e) {
this.Ui.Plugin.Store.FilterAllTabs(false);
}
private void Activated(ChatActivatedArgs args) {
this.Activate = true;
if (args.AddIfNotPresent != null && !this.Chat.Contains(args.AddIfNotPresent)) {
@ -126,10 +140,6 @@ internal sealed class ChatLog : IUiComponent {
private void ClearLog(string command, string arguments) {
switch (arguments) {
case "all":
using (var messages = this.Ui.Plugin.Store.GetMessages()) {
messages.Messages.Clear();
}
foreach (var tab in this.Ui.Plugin.Config.Tabs) {
tab.Clear();
}
@ -374,10 +384,10 @@ internal sealed class ChatLog : IUiComponent {
?.RawString ?? "???";
this.DrawChunks(new Chunk[] {
new TextChunk(null, null, "Tell "),
new TextChunk(null, null, this._tellTarget.Name),
new IconChunk(null, null, BitmapFontIcon.CrossWorld),
new TextChunk(null, null, world),
new TextChunk(ChunkSource.None, null, "Tell "),
new TextChunk(ChunkSource.None, null, this._tellTarget.Name),
new IconChunk(ChunkSource.None, null, BitmapFontIcon.CrossWorld),
new TextChunk(ChunkSource.None, null, world),
});
} else if (this._tempChannel != null) {
if (this._tempChannel.Value.IsLinkshell()) {
@ -626,7 +636,7 @@ internal sealed class ChatLog : IUiComponent {
if (table) {
ImGui.TextUnformatted(timestamp);
} else {
this.DrawChunk(new TextChunk(null, null, $"[{timestamp}]") {
this.DrawChunk(new TextChunk(ChunkSource.None, null, $"[{timestamp}]") {
Foreground = 0xFFFFFFFF,
});
ImGui.SameLine();

View File

@ -50,7 +50,7 @@ internal class CommandHelp {
return;
}
this.Log.DrawChunks(ChunkUtil.ToChunks(this.Command.Description.ToDalamudString(), null).ToList());
this.Log.DrawChunks(ChunkUtil.ToChunks(this.Command.Description.ToDalamudString(), ChunkSource.None, null).ToList());
ImGui.End();
}

View File

@ -26,6 +26,7 @@ internal sealed class Settings : IUiComponent {
new Ui.SettingsTabs.Fonts(this.Mutable),
new ChatColours(this.Mutable, this.Ui.Plugin),
new Tabs(this.Mutable),
new Database(this.Mutable, this.Ui.Plugin.Store),
new Miscellaneous(this.Mutable),
new About(),
};
@ -70,9 +71,11 @@ internal sealed class Settings : IUiComponent {
ImGui.TableNextColumn();
var changed = false;
for (var i = 0; i < this.Tabs.Count; i++) {
if (ImGui.Selectable($"{this.Tabs[i].Name}###tab-{i}", this._currentTab == i)) {
this._currentTab = i;
changed = true;
}
}
@ -84,7 +87,7 @@ internal sealed class Settings : IUiComponent {
- ImGui.GetStyle().ItemInnerSpacing.Y * 2
- ImGui.CalcTextSize("A").Y;
if (ImGui.BeginChild("##chat2-settings", new Vector2(-1, height))) {
this.Tabs[this._currentTab].Draw();
this.Tabs[this._currentTab].Draw(changed);
ImGui.EndChild();
}

View File

@ -29,7 +29,7 @@ internal sealed class About : ISettingsTab {
this._translators.Sort((a, b) => string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal));
}
public void Draw() {
public void Draw(bool changed) {
ImGui.PushTextWrapPos();
ImGui.TextUnformatted(string.Format(Language.Options_About_Opening, Plugin.PluginName));

View File

@ -32,7 +32,7 @@ internal sealed class ChatColours : ISettingsTab {
#endif
}
public void Draw() {
public void Draw(bool changed) {
foreach (var (_, types) in ChatTypeExt.SortOrder) {
foreach (var type in types) {
if (ImGuiUtil.IconButton(FontAwesomeIcon.UndoAlt, $"{type}", Language.Options_ChatColours_Reset)) {

View File

@ -0,0 +1,60 @@
using ChatTwo.Resources;
using ChatTwo.Util;
using ImGuiNET;
namespace ChatTwo.Ui.SettingsTabs;
internal sealed class Database : ISettingsTab {
private Configuration Mutable { get; }
private Store Store { get; }
public string Name => Language.Options_Database_Tab + "###tabs-database";
internal Database(Configuration mutable, Store store) {
this.Store = store;
this.Mutable = mutable;
}
private bool _showAdvanced;
public void Draw(bool changed) {
if (changed) {
this._showAdvanced = ImGui.GetIO().KeyShift;
}
ImGuiUtil.OptionCheckbox(ref this.Mutable.DatabaseBattleMessages, Language.Options_DatabaseBattleMessages_Name, Language.Options_DatabaseBattleMessages_Description);
ImGui.Spacing();
if (ImGuiUtil.OptionCheckbox(ref this.Mutable.LoadPreviousSession, Language.Options_LoadPreviousSession_Name, Language.Options_LoadPreviousSession_Description)) {
if (this.Mutable.LoadPreviousSession) {
this.Mutable.FilterIncludePreviousSessions = true;
}
}
ImGui.Spacing();
if (ImGuiUtil.OptionCheckbox(ref this.Mutable.FilterIncludePreviousSessions, Language.Options_FilterIncludePreviousSessions_Name, Language.Options_FilterIncludePreviousSessions_Description)) {
if (!this.Mutable.FilterIncludePreviousSessions) {
this.Mutable.LoadPreviousSession = false;
}
}
ImGui.Spacing();
if (this._showAdvanced && ImGui.TreeNodeEx("Advanced")) {
ImGui.PushTextWrapPos();
ImGuiUtil.WarningText("Do not click these buttons unless you know what you're doing.");
if (ImGui.Button("Checkpoint")) {
this.Store.Database.Checkpoint();
}
if (ImGui.Button("Rebuild")) {
this.Store.Database.Rebuild();
}
ImGui.PopTextWrapPos();
ImGui.TreePop();
}
}
}

View File

@ -13,7 +13,7 @@ internal sealed class Display : ISettingsTab {
this.Mutable = mutable;
}
public void Draw() {
public void Draw(bool changed) {
ImGui.PushTextWrapPos();
ImGuiUtil.OptionCheckbox(ref this.Mutable.HideChat, Language.Options_HideChat_Name, Language.Options_HideChat_Description);

View File

@ -21,8 +21,8 @@ public class Fonts : ISettingsTab {
this.JpFonts = Ui.Fonts.GetJpFonts();
}
public void Draw() {
if (ImGui.IsWindowAppearing()) {
public void Draw(bool changed) {
if (changed) {
this.UpdateFonts();
}

View File

@ -2,5 +2,5 @@
internal interface ISettingsTab {
string Name { get; }
void Draw();
void Draw(bool changed);
}

View File

@ -13,7 +13,7 @@ internal sealed class Miscellaneous : ISettingsTab {
this.Mutable = mutable;
}
public void Draw() {
public void Draw(bool changed) {
if (ImGuiUtil.BeginComboVertical(Language.Options_Language_Name, this.Mutable.LanguageOverride.Name())) {
foreach (var language in Enum.GetValues<LanguageOverride>()) {
if (ImGui.Selectable(language.Name())) {

View File

@ -17,7 +17,7 @@ internal sealed class Tabs : ISettingsTab {
this.Mutable = mutable;
}
public void Draw() {
public void Draw(bool changed) {
const string addTabPopup = "add-tab-popup";
if (ImGuiUtil.IconButton(FontAwesomeIcon.Plus, tooltip: Language.Options_Tabs_Add)) {

View File

@ -5,7 +5,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
namespace ChatTwo.Util;
internal static class ChunkUtil {
internal static IEnumerable<Chunk> ToChunks(SeString msg, ChatType? defaultColour) {
internal static IEnumerable<Chunk> ToChunks(SeString msg, ChunkSource source, ChatType? defaultColour) {
var chunks = new List<Chunk>();
var italic = false;
@ -14,7 +14,7 @@ internal static class ChunkUtil {
Payload? link = null;
void Append(string text) {
chunks.Add(new TextChunk(msg, link, text) {
chunks.Add(new TextChunk(source, link, text) {
FallbackColour = defaultColour,
Foreground = foreground.Count > 0 ? foreground.Peek() : null,
Glow = glow.Count > 0 ? glow.Peek() : null,
@ -47,13 +47,13 @@ internal static class ChunkUtil {
break;
case PayloadType.AutoTranslateText:
chunks.Add(new IconChunk(msg, link, BitmapFontIcon.AutoTranslateBegin));
chunks.Add(new IconChunk(source, link, BitmapFontIcon.AutoTranslateBegin));
var autoText = ((AutoTranslatePayload) payload).Text;
Append(autoText.Substring(2, autoText.Length - 4));
chunks.Add(new IconChunk(msg, link, BitmapFontIcon.AutoTranslateEnd));
chunks.Add(new IconChunk(source, link, BitmapFontIcon.AutoTranslateEnd));
break;
case PayloadType.Icon:
chunks.Add(new IconChunk(msg, link, ((IconPayload) payload).Icon));
chunks.Add(new IconChunk(source, link, ((IconPayload) payload).Icon));
break;
case PayloadType.MapLink:
case PayloadType.Quest: