diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj
index 4ed2c82..ab625c1 100755
--- a/ChatTwo/ChatTwo.csproj
+++ b/ChatTwo/ChatTwo.csproj
@@ -48,6 +48,7 @@
+
diff --git a/ChatTwo/Chunk.cs b/ChatTwo/Chunk.cs
index 7c6a53b..a259cf9 100755
--- a/ChatTwo/Chunk.cs
+++ b/ChatTwo/Chunk.cs
@@ -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) {
+ }
}
diff --git a/ChatTwo/Code/ChatCode.cs b/ChatTwo/Code/ChatCode.cs
index 11ddcb3..7b54853 100755
--- a/ChatTwo/Code/ChatCode.cs
+++ b/ChatTwo/Code/ChatCode.cs
@@ -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 {
diff --git a/ChatTwo/Code/ChatTypeExt.cs b/ChatTwo/Code/ChatTypeExt.cs
index 92a1ea9..92e27e5 100755
--- a/ChatTwo/Code/ChatTypeExt.cs
+++ b/ChatTwo/Code/ChatTypeExt.cs
@@ -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,
};
}
diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs
index a1d8442..827b798 100755
--- a/ChatTwo/Configuration.cs
+++ b/ChatTwo/Configuration.cs
@@ -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;
diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs
index 756f76f..8159b48 100755
--- a/ChatTwo/GameFunctions/Chat.cs
+++ b/ChatTwo/GameFunctions/Chat.cs
@@ -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();
}
diff --git a/ChatTwo/Message.cs b/ChatTwo/Message.cs
index 679100f..bef5b4c 100755
--- a/ChatTwo/Message.cs
+++ b/ChatTwo/Message.cs
@@ -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 Sender { get; }
internal List Content { get; }
- internal Message(ChatCode code, List sender, List content) {
+ internal SeString SenderSource { get; }
+ internal SeString ContentSource { get; }
+
+ internal SortCode SortCode { get; }
+
+ internal Message(ulong receiver, ChatCode code, List sender, List 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(code);
+ this.Sender = BsonMapper.Global.Deserialize>(sender);
+ this.Content = BsonMapper.Global.Deserialize>(content);
+ this.SenderSource = BsonMapper.Global.ToObject(senderSource);
+ this.ContentSource = BsonMapper.Global.ToObject(contentSource);
+ this.SortCode = BsonMapper.Global.ToObject(sortCode);
+
+ foreach (var chunk in this.Sender.Concat(this.Content)) {
+ chunk.Message = this;
+ }
+ }
}
diff --git a/ChatTwo/PayloadHandler.cs b/ChatTwo/PayloadHandler.cs
index 5c88b2a..61b3004 100755
--- a/ChatTwo/PayloadHandler.cs
+++ b/ChatTwo/PayloadHandler.cs
@@ -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()?.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 { new TextChunk(null, null, player.PlayerName) };
+ var name = new List { 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),
});
}
diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs
index fe948ec..7666e53 100755
--- a/ChatTwo/Plugin.cs
+++ b/ChatTwo/Plugin.cs
@@ -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;
}
diff --git a/ChatTwo/PluginUi.cs b/ChatTwo/PluginUi.cs
index c0e9426..8403d42 100755
--- a/ChatTwo/PluginUi.cs
+++ b/ChatTwo/PluginUi.cs
@@ -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();
}
diff --git a/ChatTwo/Resources/Language.Designer.cs b/ChatTwo/Resources/Language.Designer.cs
index 313d611..8a083ad 100755
--- a/ChatTwo/Resources/Language.Designer.cs
+++ b/ChatTwo/Resources/Language.Designer.cs
@@ -402,6 +402,33 @@ namespace ChatTwo.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Database.
+ ///
+ internal static string Options_Database_Tab {
+ get {
+ return ResourceManager.GetString("Options_Database_Tab", resourceCulture);
+ }
+ }
+
+ ///
+ /// 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..
+ ///
+ internal static string Options_DatabaseBattleMessages_Description {
+ get {
+ return ResourceManager.GetString("Options_DatabaseBattleMessages_Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Save battle messages in database.
+ ///
+ internal static string Options_DatabaseBattleMessages_Name {
+ get {
+ return ResourceManager.GetString("Options_DatabaseBattleMessages_Name", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Display.
///
@@ -411,6 +438,24 @@ namespace ChatTwo.Resources {
}
}
+ ///
+ /// 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..
+ ///
+ internal static string Options_FilterIncludePreviousSessions_Description {
+ get {
+ return ResourceManager.GetString("Options_FilterIncludePreviousSessions_Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Include previous sessions when populating tabs.
+ ///
+ internal static string Options_FilterIncludePreviousSessions_Name {
+ get {
+ return ResourceManager.GetString("Options_FilterIncludePreviousSessions_Name", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The font {0} will use to display non-Japanese text..
///
@@ -591,6 +636,24 @@ namespace ChatTwo.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to After logging in, populate the chat with the messages from when you last logged off..
+ ///
+ internal static string Options_LoadPreviousSession_Description {
+ get {
+ return ResourceManager.GetString("Options_LoadPreviousSession_Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Display previous chat session on login.
+ ///
+ internal static string Options_LoadPreviousSession_Name {
+ get {
+ return ResourceManager.GetString("Options_LoadPreviousSession_Name", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Miscellaneous.
///
diff --git a/ChatTwo/Resources/Language.resx b/ChatTwo/Resources/Language.resx
index 2a6923c..aedb6f3 100755
--- a/ChatTwo/Resources/Language.resx
+++ b/ChatTwo/Resources/Language.resx
@@ -437,4 +437,25 @@
The language to display {0} in.
+
+ Save battle messages in database
+
+
+ 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.
+
+
+ Display previous chat session on login
+
+
+ After logging in, populate the chat with the messages from when you last logged off.
+
+
+ Database
+
+
+ Include previous sessions when populating tabs
+
+
+ Include messages from before the game was launched when populating tabs. Tabs are populated when the settings are saved.
+
diff --git a/ChatTwo/Store.cs b/ChatTwo/Store.cs
index b924ab4..03cf736 100755
--- a/ChatTwo/Store.cs
+++ b/ChatTwo/Store.cs
@@ -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 Messages { get; }
-
- internal MessagesLock(List 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 Messages { get; } = new();
private ConcurrentQueue<(uint, Message)> Pending { get; } = new();
+ private Stopwatch CheckpointTimer { get; } = new();
+ internal ILiteDatabase Database { get; }
+ private ILiteCollection Messages => this.Database.GetCollection("messages");
private Dictionary 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()
+ .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 => {
+ switch (payload) {
+ case AchievementPayload achievement:
+ return new BsonDocument(new Dictionary {
+ ["Type"] = new("Achievement"),
+ ["Id"] = new(achievement.Id),
+ });
+ case PartyFinderPayload partyFinder:
+ return new BsonDocument(new Dictionary {
+ ["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();
+ 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()) {
+ 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();
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();
diff --git a/ChatTwo/Ui/ChatLog.cs b/ChatTwo/Ui/ChatLog.cs
index 3df9924..90a6f30 100755
--- a/ChatTwo/Ui/ChatLog.cs
+++ b/ChatTwo/Ui/ChatLog.cs
@@ -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();
diff --git a/ChatTwo/Ui/CommandHelp.cs b/ChatTwo/Ui/CommandHelp.cs
index b3c3e62..b188a9d 100755
--- a/ChatTwo/Ui/CommandHelp.cs
+++ b/ChatTwo/Ui/CommandHelp.cs
@@ -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();
}
diff --git a/ChatTwo/Ui/Settings.cs b/ChatTwo/Ui/Settings.cs
index d601526..b2dccec 100755
--- a/ChatTwo/Ui/Settings.cs
+++ b/ChatTwo/Ui/Settings.cs
@@ -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();
}
diff --git a/ChatTwo/Ui/SettingsTabs/About.cs b/ChatTwo/Ui/SettingsTabs/About.cs
index 1f89acc..93bfcf5 100755
--- a/ChatTwo/Ui/SettingsTabs/About.cs
+++ b/ChatTwo/Ui/SettingsTabs/About.cs
@@ -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));
diff --git a/ChatTwo/Ui/SettingsTabs/ChatColours.cs b/ChatTwo/Ui/SettingsTabs/ChatColours.cs
index dfeb028..13105da 100755
--- a/ChatTwo/Ui/SettingsTabs/ChatColours.cs
+++ b/ChatTwo/Ui/SettingsTabs/ChatColours.cs
@@ -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)) {
diff --git a/ChatTwo/Ui/SettingsTabs/Database.cs b/ChatTwo/Ui/SettingsTabs/Database.cs
new file mode 100755
index 0000000..2a74ce2
--- /dev/null
+++ b/ChatTwo/Ui/SettingsTabs/Database.cs
@@ -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();
+ }
+ }
+}
diff --git a/ChatTwo/Ui/SettingsTabs/Display.cs b/ChatTwo/Ui/SettingsTabs/Display.cs
index 716726a..830691d 100755
--- a/ChatTwo/Ui/SettingsTabs/Display.cs
+++ b/ChatTwo/Ui/SettingsTabs/Display.cs
@@ -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);
diff --git a/ChatTwo/Ui/SettingsTabs/Fonts.cs b/ChatTwo/Ui/SettingsTabs/Fonts.cs
index 7287a59..fd1d2ee 100755
--- a/ChatTwo/Ui/SettingsTabs/Fonts.cs
+++ b/ChatTwo/Ui/SettingsTabs/Fonts.cs
@@ -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();
}
diff --git a/ChatTwo/Ui/SettingsTabs/ISettingsTab.cs b/ChatTwo/Ui/SettingsTabs/ISettingsTab.cs
index f62573d..aecbe69 100755
--- a/ChatTwo/Ui/SettingsTabs/ISettingsTab.cs
+++ b/ChatTwo/Ui/SettingsTabs/ISettingsTab.cs
@@ -2,5 +2,5 @@
internal interface ISettingsTab {
string Name { get; }
- void Draw();
+ void Draw(bool changed);
}
diff --git a/ChatTwo/Ui/SettingsTabs/Miscellaneous.cs b/ChatTwo/Ui/SettingsTabs/Miscellaneous.cs
index 250332f..fd0f6e6 100755
--- a/ChatTwo/Ui/SettingsTabs/Miscellaneous.cs
+++ b/ChatTwo/Ui/SettingsTabs/Miscellaneous.cs
@@ -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()) {
if (ImGui.Selectable(language.Name())) {
diff --git a/ChatTwo/Ui/SettingsTabs/Tabs.cs b/ChatTwo/Ui/SettingsTabs/Tabs.cs
index ce31cad..53fc9e1 100755
--- a/ChatTwo/Ui/SettingsTabs/Tabs.cs
+++ b/ChatTwo/Ui/SettingsTabs/Tabs.cs
@@ -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)) {
diff --git a/ChatTwo/Util/ChunkUtil.cs b/ChatTwo/Util/ChunkUtil.cs
index 4674da1..7d85de7 100755
--- a/ChatTwo/Util/ChunkUtil.cs
+++ b/ChatTwo/Util/ChunkUtil.cs
@@ -5,7 +5,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
namespace ChatTwo.Util;
internal static class ChunkUtil {
- internal static IEnumerable ToChunks(SeString msg, ChatType? defaultColour) {
+ internal static IEnumerable ToChunks(SeString msg, ChunkSource source, ChatType? defaultColour) {
var chunks = new List();
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: