chore: initial commit
This commit is contained in:
Executable
+59
@@ -0,0 +1,59 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0</Version>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Dalamud>$(AppData)\XIVLauncher\addon\Hooks\dev</Dalamud>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(IsCI)' == 'true'">
|
||||
<Dalamud>$(HOME)/dalamud</Dalamud>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(Dalamud)\Dalamud.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(Dalamud)\FFXIVClientStructs.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(Dalamud)\ImGui.NET.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(Dalamud)\ImGuiScene.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(Dalamud)\Lumina.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(Dalamud)\Lumina.Excel.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="XivCommon" Version="4.0.0-alpha.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="fonts\NotoSans-Italic.ttf" />
|
||||
<EmbeddedResource Include="fonts\NotoSans-Regular.ttf" />
|
||||
<EmbeddedResource Include="fonts\NotoSansJP-Regular.otf" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
using ChatTwo.Code;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
internal abstract class Chunk {
|
||||
}
|
||||
|
||||
internal class TextChunk : Chunk {
|
||||
internal ChatType? FallbackColour { get; set; }
|
||||
internal uint? Foreground { get; set; }
|
||||
internal uint? Glow { get; set; }
|
||||
internal bool Italic { get; set; }
|
||||
internal string Content { get; set; }
|
||||
|
||||
internal TextChunk(string content) {
|
||||
this.Content = content;
|
||||
}
|
||||
|
||||
internal TextChunk(ChatType? fallbackColour, uint? foreground, uint? glow, bool italic, string content) {
|
||||
this.FallbackColour = fallbackColour;
|
||||
this.Foreground = foreground;
|
||||
this.Glow = glow;
|
||||
this.Italic = italic;
|
||||
this.Content = content;
|
||||
}
|
||||
}
|
||||
|
||||
internal class IconChunk : Chunk {
|
||||
internal BitmapFontIcon Icon;
|
||||
|
||||
public IconChunk(BitmapFontIcon icon) {
|
||||
this.Icon = icon;
|
||||
}
|
||||
}
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
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);
|
||||
private ChatSource SourceFrom(ushort shift) => (ChatSource) (1 << ((this.Raw >> shift) & 0xF));
|
||||
|
||||
internal ChatCode(ushort raw) {
|
||||
this.Raw = raw;
|
||||
}
|
||||
|
||||
internal ChatType Parent() => this.Type switch {
|
||||
ChatType.Say => ChatType.Say,
|
||||
ChatType.GmSay => ChatType.Say,
|
||||
ChatType.Shout => ChatType.Shout,
|
||||
ChatType.GmShout => ChatType.Shout,
|
||||
ChatType.TellOutgoing => ChatType.TellOutgoing,
|
||||
ChatType.TellIncoming => ChatType.TellOutgoing,
|
||||
ChatType.GmTell => ChatType.TellOutgoing,
|
||||
ChatType.Party => ChatType.Party,
|
||||
ChatType.CrossParty => ChatType.Party,
|
||||
ChatType.GmParty => ChatType.Party,
|
||||
ChatType.Linkshell1 => ChatType.Linkshell1,
|
||||
ChatType.GmLinkshell1 => ChatType.Linkshell1,
|
||||
ChatType.Linkshell2 => ChatType.Linkshell2,
|
||||
ChatType.GmLinkshell2 => ChatType.Linkshell2,
|
||||
ChatType.Linkshell3 => ChatType.Linkshell3,
|
||||
ChatType.GmLinkshell3 => ChatType.Linkshell3,
|
||||
ChatType.Linkshell4 => ChatType.Linkshell4,
|
||||
ChatType.GmLinkshell4 => ChatType.Linkshell4,
|
||||
ChatType.Linkshell5 => ChatType.Linkshell5,
|
||||
ChatType.GmLinkshell5 => ChatType.Linkshell5,
|
||||
ChatType.Linkshell6 => ChatType.Linkshell6,
|
||||
ChatType.GmLinkshell6 => ChatType.Linkshell6,
|
||||
ChatType.Linkshell7 => ChatType.Linkshell7,
|
||||
ChatType.GmLinkshell7 => ChatType.Linkshell7,
|
||||
ChatType.Linkshell8 => ChatType.Linkshell8,
|
||||
ChatType.GmLinkshell8 => ChatType.Linkshell8,
|
||||
ChatType.FreeCompany => ChatType.FreeCompany,
|
||||
ChatType.GmFreeCompany => ChatType.FreeCompany,
|
||||
ChatType.NoviceNetwork => ChatType.NoviceNetwork,
|
||||
ChatType.GmNoviceNetwork => ChatType.NoviceNetwork,
|
||||
ChatType.CustomEmote => ChatType.CustomEmote,
|
||||
ChatType.StandardEmote => ChatType.StandardEmote,
|
||||
ChatType.Yell => ChatType.Yell,
|
||||
ChatType.GmYell => ChatType.Yell,
|
||||
ChatType.GainBuff => ChatType.GainBuff,
|
||||
ChatType.LoseBuff => ChatType.GainBuff,
|
||||
ChatType.GainDebuff => ChatType.GainDebuff,
|
||||
ChatType.LoseDebuff => ChatType.GainDebuff,
|
||||
ChatType.System => ChatType.System,
|
||||
ChatType.Alarm => ChatType.System,
|
||||
ChatType.RetainerSale => ChatType.System,
|
||||
ChatType.PeriodicRecruitmentNotification => ChatType.System,
|
||||
ChatType.Sign => ChatType.System,
|
||||
ChatType.Orchestrion => ChatType.System,
|
||||
ChatType.MessageBook => ChatType.System,
|
||||
ChatType.NpcDialogue => ChatType.NpcDialogue,
|
||||
ChatType.NpcAnnouncement => ChatType.NpcDialogue,
|
||||
ChatType.LootRoll => ChatType.LootRoll,
|
||||
ChatType.RandomNumber => ChatType.LootRoll,
|
||||
ChatType.FreeCompanyAnnouncement => ChatType.FreeCompanyAnnouncement,
|
||||
ChatType.FreeCompanyLoginLogout => ChatType.FreeCompanyAnnouncement,
|
||||
ChatType.PvpTeamAnnouncement => ChatType.PvpTeamAnnouncement,
|
||||
ChatType.PvpTeamLoginLogout => ChatType.PvpTeamAnnouncement,
|
||||
_ => this.Type,
|
||||
};
|
||||
|
||||
internal bool IsBattle() {
|
||||
switch (this.Type) {
|
||||
case ChatType.Damage:
|
||||
case ChatType.Miss:
|
||||
case ChatType.Action:
|
||||
case ChatType.Item:
|
||||
case ChatType.Healing:
|
||||
case ChatType.GainBuff:
|
||||
case ChatType.LoseBuff:
|
||||
case ChatType.GainDebuff:
|
||||
case ChatType.LoseDebuff:
|
||||
case ChatType.BattleSystem:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
namespace ChatTwo.Code;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")]
|
||||
[Flags]
|
||||
internal enum ChatSource : ushort {
|
||||
Self = 2,
|
||||
PartyMember = 4,
|
||||
AllianceMember = 8,
|
||||
Other = 16,
|
||||
EngagedEnemy = 32,
|
||||
UnengagedEnemy = 64,
|
||||
FriendlyNpc = 128,
|
||||
SelfPet = 256,
|
||||
PartyPet = 512,
|
||||
AlliancePet = 1024,
|
||||
OtherPet = 2048,
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
namespace ChatTwo.Code;
|
||||
|
||||
internal static class ChatSourceExt {
|
||||
internal const ChatSource All =
|
||||
ChatSource.Self
|
||||
| ChatSource.PartyMember
|
||||
| ChatSource.AllianceMember
|
||||
| ChatSource.Other
|
||||
| ChatSource.EngagedEnemy
|
||||
| ChatSource.UnengagedEnemy
|
||||
| ChatSource.FriendlyNpc
|
||||
| ChatSource.SelfPet
|
||||
| ChatSource.PartyPet
|
||||
| ChatSource.AlliancePet
|
||||
| ChatSource.OtherPet;
|
||||
}
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
namespace ChatTwo.Code;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")]
|
||||
internal enum ChatType : ushort {
|
||||
Debug = 1,
|
||||
Urgent = 2,
|
||||
Notice = 3,
|
||||
Say = 10,
|
||||
Shout = 11,
|
||||
TellOutgoing = 12,
|
||||
TellIncoming = 13,
|
||||
Party = 14,
|
||||
Alliance = 15,
|
||||
Linkshell1 = 16,
|
||||
Linkshell2 = 17,
|
||||
Linkshell3 = 18,
|
||||
Linkshell4 = 19,
|
||||
Linkshell5 = 20,
|
||||
Linkshell6 = 21,
|
||||
Linkshell7 = 22,
|
||||
Linkshell8 = 23,
|
||||
FreeCompany = 24,
|
||||
NoviceNetwork = 27,
|
||||
CustomEmote = 28,
|
||||
StandardEmote = 29,
|
||||
Yell = 30,
|
||||
|
||||
// 31 - also party?
|
||||
CrossParty = 32,
|
||||
PvpTeam = 36,
|
||||
CrossLinkshell1 = 37,
|
||||
Damage = 41,
|
||||
Miss = 42,
|
||||
Action = 43,
|
||||
Item = 44,
|
||||
Healing = 45,
|
||||
GainBuff = 46,
|
||||
GainDebuff = 47,
|
||||
LoseBuff = 48,
|
||||
LoseDebuff = 49,
|
||||
Alarm = 55,
|
||||
Echo = 56,
|
||||
System = 57,
|
||||
BattleSystem = 58,
|
||||
GatheringSystem = 59,
|
||||
Error = 60,
|
||||
NpcDialogue = 61,
|
||||
LootNotice = 62,
|
||||
Progress = 64,
|
||||
LootRoll = 65,
|
||||
Crafting = 66,
|
||||
Gathering = 67,
|
||||
NpcAnnouncement = 68,
|
||||
FreeCompanyAnnouncement = 69,
|
||||
FreeCompanyLoginLogout = 70,
|
||||
RetainerSale = 71,
|
||||
PeriodicRecruitmentNotification = 72,
|
||||
Sign = 73,
|
||||
RandomNumber = 74,
|
||||
NoviceNetworkSystem = 75,
|
||||
Orchestrion = 76,
|
||||
PvpTeamAnnouncement = 77,
|
||||
PvpTeamLoginLogout = 78,
|
||||
MessageBook = 79,
|
||||
GmTell = 80,
|
||||
GmSay = 81,
|
||||
GmShout = 82,
|
||||
GmYell = 83,
|
||||
GmParty = 84,
|
||||
GmFreeCompany = 85,
|
||||
GmLinkshell1 = 86,
|
||||
GmLinkshell2 = 87,
|
||||
GmLinkshell3 = 88,
|
||||
GmLinkshell4 = 89,
|
||||
GmLinkshell5 = 90,
|
||||
GmLinkshell6 = 91,
|
||||
GmLinkshell7 = 92,
|
||||
GmLinkshell8 = 93,
|
||||
GmNoviceNetwork = 94,
|
||||
CrossLinkshell2 = 101,
|
||||
CrossLinkshell3 = 102,
|
||||
CrossLinkshell4 = 103,
|
||||
CrossLinkshell5 = 104,
|
||||
CrossLinkshell6 = 105,
|
||||
CrossLinkshell7 = 106,
|
||||
CrossLinkshell8 = 107,
|
||||
}
|
||||
Executable
+209
@@ -0,0 +1,209 @@
|
||||
using ChatTwo.Util;
|
||||
|
||||
namespace ChatTwo.Code;
|
||||
|
||||
internal static class ChatTypeExt {
|
||||
internal static string? Name(this ChatType type) {
|
||||
return type switch {
|
||||
ChatType.Debug => "Debug",
|
||||
ChatType.Urgent => "Urgent",
|
||||
ChatType.Notice => "Notice",
|
||||
ChatType.Say => "Say",
|
||||
ChatType.Shout => "Shout",
|
||||
ChatType.TellOutgoing => "Tell (Outgoing)",
|
||||
ChatType.TellIncoming => "Tell (Incoming)",
|
||||
ChatType.Party => "Party",
|
||||
ChatType.Alliance => "Alliance",
|
||||
ChatType.Linkshell1 => "Linkshell [1]",
|
||||
ChatType.Linkshell2 => "Linkshell [2]",
|
||||
ChatType.Linkshell3 => "Linkshell [3]",
|
||||
ChatType.Linkshell4 => "Linkshell [4]",
|
||||
ChatType.Linkshell5 => "Linkshell [5]",
|
||||
ChatType.Linkshell6 => "Linkshell [6]",
|
||||
ChatType.Linkshell7 => "Linkshell [7]",
|
||||
ChatType.Linkshell8 => "Linkshell [8]",
|
||||
ChatType.FreeCompany => "Free Company",
|
||||
ChatType.NoviceNetwork => "Novice Network",
|
||||
ChatType.CustomEmote => "Custom Emotes",
|
||||
ChatType.StandardEmote => "Standard Emotes",
|
||||
ChatType.Yell => "Yell",
|
||||
ChatType.CrossParty => "Cross-world Party",
|
||||
ChatType.PvpTeam => "PvP Team",
|
||||
ChatType.CrossLinkshell1 => "Cross-world Linkshell [1]",
|
||||
ChatType.Damage => "Damage dealt",
|
||||
ChatType.Miss => "Failed attacks",
|
||||
ChatType.Action => "Actions used",
|
||||
ChatType.Item => "Items used",
|
||||
ChatType.Healing => "Healing",
|
||||
ChatType.GainBuff => "Beneficial effects granted",
|
||||
ChatType.GainDebuff => "Detrimental effects inflicted",
|
||||
ChatType.LoseBuff => "Beneficial effects lost",
|
||||
ChatType.LoseDebuff => "Detrimental effects cured",
|
||||
ChatType.Alarm => "Alarm Notifications",
|
||||
ChatType.Echo => "Echo",
|
||||
ChatType.System => "System Messages",
|
||||
ChatType.BattleSystem => "Battle System Messages",
|
||||
ChatType.GatheringSystem => "Gathering System Messages",
|
||||
ChatType.Error => "Error Messages",
|
||||
ChatType.NpcDialogue => "NPC Dialogue",
|
||||
ChatType.LootNotice => "Loot Notices",
|
||||
ChatType.Progress => "Progression Messages",
|
||||
ChatType.LootRoll => "Loot Messages",
|
||||
ChatType.Crafting => "Synthesis Messages",
|
||||
ChatType.Gathering => "Gathering Messages",
|
||||
ChatType.NpcAnnouncement => "NPC Dialogue (Announcements)",
|
||||
ChatType.FreeCompanyAnnouncement => "Free Company Announcements",
|
||||
ChatType.FreeCompanyLoginLogout => "Free Company Member Login Notifications",
|
||||
ChatType.RetainerSale => "Retainer Sale Notifications",
|
||||
ChatType.PeriodicRecruitmentNotification => "Periodic Recruitment Notifications",
|
||||
ChatType.Sign => "Sign Messages for PC Targets",
|
||||
ChatType.RandomNumber => "Random Number Messages",
|
||||
ChatType.NoviceNetworkSystem => "Novice Network Notifications",
|
||||
ChatType.Orchestrion => "Current Orchestrion Track Messages",
|
||||
ChatType.PvpTeamAnnouncement => "PvP Team Announcements",
|
||||
ChatType.PvpTeamLoginLogout => "PvP Team Member Login Notifications",
|
||||
ChatType.MessageBook => "Message Book Alert",
|
||||
ChatType.GmTell => "Tell (GM)",
|
||||
ChatType.GmSay => "Say (GM)",
|
||||
ChatType.GmShout => "Shout (GM)",
|
||||
ChatType.GmYell => "Yell (GM)",
|
||||
ChatType.GmParty => "Party (GM)",
|
||||
ChatType.GmFreeCompany => "Free Company (GM)",
|
||||
ChatType.GmLinkshell1 => "Linkshell [1] (GM)",
|
||||
ChatType.GmLinkshell2 => "Linkshell [2] (GM)",
|
||||
ChatType.GmLinkshell3 => "Linkshell [3] (GM)",
|
||||
ChatType.GmLinkshell4 => "Linkshell [4] (GM)",
|
||||
ChatType.GmLinkshell5 => "Linkshell [5] (GM)",
|
||||
ChatType.GmLinkshell6 => "Linkshell [6] (GM)",
|
||||
ChatType.GmLinkshell7 => "Linkshell [7] (GM)",
|
||||
ChatType.GmLinkshell8 => "Linkshell [8] (GM)",
|
||||
ChatType.GmNoviceNetwork => "Novice Network (GM)",
|
||||
ChatType.CrossLinkshell2 => "Cross-world Linkshell [2]",
|
||||
ChatType.CrossLinkshell3 => "Cross-world Linkshell [3]",
|
||||
ChatType.CrossLinkshell4 => "Cross-world Linkshell [4]",
|
||||
ChatType.CrossLinkshell5 => "Cross-world Linkshell [5]",
|
||||
ChatType.CrossLinkshell6 => "Cross-world Linkshell [6]",
|
||||
ChatType.CrossLinkshell7 => "Cross-world Linkshell [7]",
|
||||
ChatType.CrossLinkshell8 => "Cross-world Linkshell [8]",
|
||||
_ => type.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
internal static uint? DefaultColour(this ChatType type) {
|
||||
switch (type) {
|
||||
case ChatType.Debug:
|
||||
return ColourUtil.ComponentsToRgba(204, 204, 204);
|
||||
case ChatType.Urgent:
|
||||
return ColourUtil.ComponentsToRgba(255, 127, 127);
|
||||
case ChatType.Notice:
|
||||
return ColourUtil.ComponentsToRgba(179, 140, 255);
|
||||
|
||||
case ChatType.Say:
|
||||
case ChatType.GmSay:
|
||||
return ColourUtil.ComponentsToRgba(247, 247, 247);
|
||||
case ChatType.Shout:
|
||||
case ChatType.GmShout:
|
||||
return ColourUtil.ComponentsToRgba(255, 166, 102);
|
||||
case ChatType.TellIncoming:
|
||||
case ChatType.TellOutgoing:
|
||||
case ChatType.GmTell:
|
||||
return ColourUtil.ComponentsToRgba(255, 184, 222);
|
||||
case ChatType.Party:
|
||||
case ChatType.CrossParty:
|
||||
case ChatType.GmParty:
|
||||
return ColourUtil.ComponentsToRgba(102, 229, 255);
|
||||
case ChatType.Alliance:
|
||||
return ColourUtil.ComponentsToRgba(255, 127, 0);
|
||||
case ChatType.NoviceNetwork:
|
||||
case ChatType.NoviceNetworkSystem:
|
||||
case ChatType.GmNoviceNetwork:
|
||||
return ColourUtil.ComponentsToRgba(212, 255, 125);
|
||||
case ChatType.Linkshell1:
|
||||
case ChatType.Linkshell2:
|
||||
case ChatType.Linkshell3:
|
||||
case ChatType.Linkshell4:
|
||||
case ChatType.Linkshell5:
|
||||
case ChatType.Linkshell6:
|
||||
case ChatType.Linkshell7:
|
||||
case ChatType.Linkshell8:
|
||||
case ChatType.CrossLinkshell1:
|
||||
case ChatType.CrossLinkshell2:
|
||||
case ChatType.CrossLinkshell3:
|
||||
case ChatType.CrossLinkshell4:
|
||||
case ChatType.CrossLinkshell5:
|
||||
case ChatType.CrossLinkshell6:
|
||||
case ChatType.CrossLinkshell7:
|
||||
case ChatType.CrossLinkshell8:
|
||||
case ChatType.GmLinkshell1:
|
||||
case ChatType.GmLinkshell2:
|
||||
case ChatType.GmLinkshell3:
|
||||
case ChatType.GmLinkshell4:
|
||||
case ChatType.GmLinkshell5:
|
||||
case ChatType.GmLinkshell6:
|
||||
case ChatType.GmLinkshell7:
|
||||
case ChatType.GmLinkshell8:
|
||||
return ColourUtil.ComponentsToRgba(212, 255, 125);
|
||||
case ChatType.StandardEmote:
|
||||
return ColourUtil.ComponentsToRgba(186, 255, 240);
|
||||
case ChatType.CustomEmote:
|
||||
return ColourUtil.ComponentsToRgba(186, 255, 240);
|
||||
case ChatType.Yell:
|
||||
case ChatType.GmYell:
|
||||
return ColourUtil.ComponentsToRgba(255, 255, 0);
|
||||
case ChatType.Echo:
|
||||
return ColourUtil.ComponentsToRgba(204, 204, 204);
|
||||
case ChatType.System:
|
||||
case ChatType.GatheringSystem:
|
||||
case ChatType.PeriodicRecruitmentNotification:
|
||||
case ChatType.Orchestrion:
|
||||
case ChatType.Alarm:
|
||||
case ChatType.RetainerSale:
|
||||
case ChatType.Sign:
|
||||
case ChatType.MessageBook:
|
||||
return ColourUtil.ComponentsToRgba(204, 204, 204);
|
||||
case ChatType.NpcAnnouncement:
|
||||
case ChatType.NpcDialogue:
|
||||
return ColourUtil.ComponentsToRgba(171, 214, 71);
|
||||
case ChatType.Error:
|
||||
return ColourUtil.ComponentsToRgba(255, 74, 74);
|
||||
case ChatType.FreeCompany:
|
||||
case ChatType.FreeCompanyAnnouncement:
|
||||
case ChatType.FreeCompanyLoginLogout:
|
||||
case ChatType.GmFreeCompany:
|
||||
return ColourUtil.ComponentsToRgba(171, 219, 229);
|
||||
case ChatType.PvpTeam:
|
||||
return ColourUtil.ComponentsToRgba(171, 219, 229);
|
||||
case ChatType.PvpTeamAnnouncement:
|
||||
case ChatType.PvpTeamLoginLogout:
|
||||
return ColourUtil.ComponentsToRgba(171, 219, 229);
|
||||
case ChatType.Action:
|
||||
case ChatType.Item:
|
||||
case ChatType.LootNotice:
|
||||
return ColourUtil.ComponentsToRgba(255, 255, 176);
|
||||
case ChatType.Progress:
|
||||
return ColourUtil.ComponentsToRgba(255, 222, 115);
|
||||
case ChatType.LootRoll:
|
||||
case ChatType.RandomNumber:
|
||||
return ColourUtil.ComponentsToRgba(199, 191, 158);
|
||||
case ChatType.Crafting:
|
||||
case ChatType.Gathering:
|
||||
return ColourUtil.ComponentsToRgba(222, 191, 247);
|
||||
case ChatType.Damage:
|
||||
return ColourUtil.ComponentsToRgba(255, 125, 125);
|
||||
case ChatType.Miss:
|
||||
return ColourUtil.ComponentsToRgba(204, 204, 204);
|
||||
case ChatType.Healing:
|
||||
return ColourUtil.ComponentsToRgba(212, 255, 125);
|
||||
case ChatType.GainBuff:
|
||||
case ChatType.LoseBuff:
|
||||
return ColourUtil.ComponentsToRgba(148, 191, 255);
|
||||
case ChatType.GainDebuff:
|
||||
case ChatType.LoseDebuff:
|
||||
return ColourUtil.ComponentsToRgba(255, 138, 196);
|
||||
case ChatType.BattleSystem:
|
||||
return ColourUtil.ComponentsToRgba(204, 204, 204);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
namespace ChatTwo.Code;
|
||||
|
||||
internal enum InputChannel : uint {
|
||||
Tell = 0,
|
||||
Say = 1,
|
||||
Party = 2,
|
||||
Alliance = 3,
|
||||
Yell = 4,
|
||||
Shout = 5,
|
||||
FreeCompany = 6,
|
||||
PvpTeam = 7,
|
||||
NoviceNetwork = 8,
|
||||
CrossLinkshell1 = 9,
|
||||
CrossLinkshell2 = 10,
|
||||
CrossLinkshell3 = 11,
|
||||
CrossLinkshell4 = 12,
|
||||
CrossLinkshell5 = 13,
|
||||
CrossLinkshell6 = 14,
|
||||
CrossLinkshell7 = 15,
|
||||
CrossLinkshell8 = 16,
|
||||
|
||||
// 17 - unused?
|
||||
// 18 - unused?
|
||||
Linkshell1 = 19,
|
||||
Linkshell2 = 20,
|
||||
Linkshell3 = 21,
|
||||
Linkshell4 = 22,
|
||||
Linkshell5 = 23,
|
||||
Linkshell6 = 24,
|
||||
Linkshell7 = 25,
|
||||
Linkshell8 = 26,
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
namespace ChatTwo.Code;
|
||||
|
||||
internal static class InputChannelExt {
|
||||
internal static ChatType ToChatType(this InputChannel input) {
|
||||
return input switch {
|
||||
InputChannel.Tell => ChatType.TellOutgoing,
|
||||
InputChannel.Say => ChatType.Say,
|
||||
InputChannel.Party => ChatType.Party,
|
||||
InputChannel.Alliance => ChatType.Alliance,
|
||||
InputChannel.Yell => ChatType.Yell,
|
||||
InputChannel.Shout => ChatType.Shout,
|
||||
InputChannel.FreeCompany => ChatType.FreeCompany,
|
||||
InputChannel.PvpTeam => ChatType.PvpTeam,
|
||||
InputChannel.NoviceNetwork => ChatType.NoviceNetwork,
|
||||
InputChannel.CrossLinkshell1 => ChatType.CrossLinkshell1,
|
||||
InputChannel.CrossLinkshell2 => ChatType.CrossLinkshell2,
|
||||
InputChannel.CrossLinkshell3 => ChatType.CrossLinkshell3,
|
||||
InputChannel.CrossLinkshell4 => ChatType.CrossLinkshell4,
|
||||
InputChannel.CrossLinkshell5 => ChatType.CrossLinkshell5,
|
||||
InputChannel.CrossLinkshell6 => ChatType.CrossLinkshell6,
|
||||
InputChannel.CrossLinkshell7 => ChatType.CrossLinkshell7,
|
||||
InputChannel.CrossLinkshell8 => ChatType.CrossLinkshell8,
|
||||
InputChannel.Linkshell1 => ChatType.Linkshell1,
|
||||
InputChannel.Linkshell2 => ChatType.Linkshell2,
|
||||
InputChannel.Linkshell3 => ChatType.Linkshell3,
|
||||
InputChannel.Linkshell4 => ChatType.Linkshell4,
|
||||
InputChannel.Linkshell5 => ChatType.Linkshell5,
|
||||
InputChannel.Linkshell6 => ChatType.Linkshell6,
|
||||
InputChannel.Linkshell7 => ChatType.Linkshell7,
|
||||
InputChannel.Linkshell8 => ChatType.Linkshell8,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(input), input, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
using ChatTwo.Code;
|
||||
using Dalamud.Configuration;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
[Serializable]
|
||||
internal class Configuration : IPluginConfiguration {
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
public bool HideChat = true;
|
||||
public float FontSize = 17f;
|
||||
public Dictionary<ChatType, uint> ChatColours = new();
|
||||
public List<Tab> Tabs = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class Tab {
|
||||
public string Name = "New tab";
|
||||
public Dictionary<ChatType, ChatSource> ChatCodes = new();
|
||||
public bool DisplayUnread = true;
|
||||
public bool DisplayTimestamp = true;
|
||||
|
||||
[NonSerialized]
|
||||
public uint Unread;
|
||||
|
||||
[NonSerialized]
|
||||
public Mutex MessagesMutex = new();
|
||||
|
||||
[NonSerialized]
|
||||
public List<Message> Messages = new();
|
||||
|
||||
~Tab() {
|
||||
this.MessagesMutex.Dispose();
|
||||
}
|
||||
|
||||
internal bool Matches(Message message) {
|
||||
return this.ChatCodes.TryGetValue(message.Code.Type, out var sources) && (message.Code.Source is 0 or (ChatSource) 1 || sources.HasFlag(message.Code.Source));
|
||||
}
|
||||
|
||||
internal void AddMessage(Message message) {
|
||||
this.MessagesMutex.WaitOne();
|
||||
this.Messages.Add(message);
|
||||
if (this.Messages.Count > 1000) {
|
||||
this.Messages.RemoveAt(0);
|
||||
}
|
||||
this.MessagesMutex.ReleaseMutex();
|
||||
|
||||
this.Unread += 1;
|
||||
}
|
||||
|
||||
internal Tab Clone() {
|
||||
return new Tab {
|
||||
Name = this.Name,
|
||||
ChatCodes = this.ChatCodes.ToDictionary(entry => entry.Key, entry => entry.Value),
|
||||
DisplayUnread = this.DisplayUnread,
|
||||
DisplayTimestamp = this.DisplayTimestamp,
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
+171
@@ -0,0 +1,171 @@
|
||||
using ChatTwo.Code;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
internal unsafe class GameFunctions : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string ChatLogRefresh = "40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA";
|
||||
internal const string ChangeChannelName = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6";
|
||||
}
|
||||
|
||||
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
|
||||
|
||||
private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent);
|
||||
|
||||
internal delegate void ChatActivatedEventDelegate(string? input);
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; }
|
||||
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; }
|
||||
|
||||
internal event ChatActivatedEventDelegate? ChatActivated;
|
||||
|
||||
internal (InputChannel channel, string name) ChatChannel { get; private set; }
|
||||
|
||||
internal GameFunctions(Plugin plugin) {
|
||||
this.Plugin = plugin;
|
||||
|
||||
if (this.Plugin.SigScanner.TryScanText(Signatures.ChatLogRefresh, out var chatLogPtr)) {
|
||||
this.ChatLogRefreshHook = new Hook<ChatLogRefreshDelegate>(chatLogPtr, this.ChatLogRefreshDetour);
|
||||
this.ChatLogRefreshHook.Enable();
|
||||
}
|
||||
|
||||
if (this.Plugin.SigScanner.TryScanText(Signatures.ChangeChannelName, out var channelNamePtr)) {
|
||||
this.ChangeChannelNameHook = new Hook<ChangeChannelNameDelegate>(channelNamePtr, this.ChangeChannelNameDetour);
|
||||
this.ChangeChannelNameHook.Enable();
|
||||
}
|
||||
|
||||
this.Plugin.ClientState.Login += this.Login;
|
||||
this.Login(null, null);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Plugin.ClientState.Login -= this.Login;
|
||||
this.ChangeChannelNameHook?.Dispose();
|
||||
this.ChatLogRefreshHook?.Dispose();
|
||||
this.ChatActivated = null;
|
||||
}
|
||||
|
||||
private void Login(object? sender, EventArgs? e) {
|
||||
if (this.ChangeChannelNameHook == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
|
||||
if (agent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ChangeChannelNameDetour((IntPtr) agent);
|
||||
}
|
||||
|
||||
internal static void SetAddonInteractable(string name, bool interactable) {
|
||||
var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager;
|
||||
|
||||
var addon = (IntPtr) unitManager->GetAddonByName(name);
|
||||
if (addon == IntPtr.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
var flags = (uint*) (addon + 0x180);
|
||||
if (interactable) {
|
||||
*flags &= ~(1u << 22);
|
||||
} else {
|
||||
*flags |= 1 << 22;
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetChatInteractable(bool interactable) {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
SetAddonInteractable($"ChatLogPanel_{i}", interactable);
|
||||
}
|
||||
|
||||
SetAddonInteractable("ChatLog", interactable);
|
||||
}
|
||||
|
||||
internal static bool IsAddonInteractable(string name) {
|
||||
var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager;
|
||||
|
||||
var addon = (IntPtr) unitManager->GetAddonByName(name);
|
||||
if (addon == IntPtr.Zero) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var flags = (uint*) (addon + 0x180);
|
||||
return (*flags & (1 << 22)) == 0;
|
||||
}
|
||||
|
||||
private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) {
|
||||
if (eventId == 0x31 && value != null && value->UInt is 0x05 or 0x0C) {
|
||||
string? eventInput = null;
|
||||
|
||||
var str = value + 2;
|
||||
if (str != null && str->String != null) {
|
||||
var input = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String);
|
||||
if (input.Length > 0) {
|
||||
eventInput = input;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.ChatActivated?.Invoke(eventInput);
|
||||
} catch (Exception ex) {
|
||||
PluginLog.LogError(ex, "Error in ChatActivated event");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.ChatLogRefreshHook!.Original(log, eventId, value);
|
||||
}
|
||||
|
||||
private IntPtr ChangeChannelNameDetour(IntPtr agent) {
|
||||
// Last ShB patch
|
||||
// +0x40 = chat channel (byte or uint?)
|
||||
// channel is 17 (maybe 18?) for tells
|
||||
// +0x48 = pointer to channel name string
|
||||
var ret = this.ChangeChannelNameHook!.Original(agent);
|
||||
if (agent == IntPtr.Zero) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// E8 ?? ?? ?? ?? 8D 48 F7
|
||||
// RaptureShellModule + 0xFD0
|
||||
var shellModule = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule();
|
||||
if (shellModule == IntPtr.Zero) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
var channel = *(uint*) (shellModule + 0xFD0);
|
||||
|
||||
// var channel = *(uint*) (agent + 0x40);
|
||||
if (channel is 17 or 18) {
|
||||
channel = 0;
|
||||
}
|
||||
|
||||
SeString? name = null;
|
||||
var namePtrPtr = (byte**) (agent + 0x48);
|
||||
if (namePtrPtr != null) {
|
||||
var namePtr = *namePtrPtr;
|
||||
name = MemoryHelper.ReadSeStringNullTerminated((IntPtr) namePtr);
|
||||
if (name.Payloads.Count == 0) {
|
||||
name = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
this.ChatChannel = ((InputChannel) channel, name.TextValue.TrimStart('\uE01E').Trim());
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
using ChatTwo.Code;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
internal class Message {
|
||||
internal DateTime Date { get; }
|
||||
internal ChatCode Code { get; }
|
||||
internal List<Chunk> Sender { get; }
|
||||
internal List<Chunk> Content { get; }
|
||||
|
||||
internal Message(ChatCode code, List<Chunk> sender, List<Chunk> content) {
|
||||
this.Date = DateTime.UtcNow;
|
||||
this.Code = code;
|
||||
this.Sender = sender;
|
||||
this.Content = content;
|
||||
}
|
||||
}
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using XivCommon;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public sealed class Plugin : IDalamudPlugin {
|
||||
public string Name => "Chat 2";
|
||||
|
||||
[PluginService]
|
||||
internal DalamudPluginInterface Interface { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal ChatGui ChatGui { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal ClientState ClientState { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal CommandManager CommandManager { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal DataManager DataManager { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal Framework Framework { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal SigScanner SigScanner { get; init; }
|
||||
|
||||
internal Configuration Config { get; }
|
||||
internal XivCommonBase Common { get; }
|
||||
internal GameFunctions Functions { get; }
|
||||
internal Store Store { get; }
|
||||
internal PluginUi Ui { get; }
|
||||
|
||||
#pragma warning disable CS8618
|
||||
public Plugin() {
|
||||
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
this.Common = new XivCommonBase();
|
||||
this.Functions = new GameFunctions(this);
|
||||
this.Store = new Store(this);
|
||||
this.Ui = new PluginUi(this);
|
||||
|
||||
this.Framework!.Update += this.FrameworkUpdate;
|
||||
}
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public void Dispose() {
|
||||
this.Framework.Update -= this.FrameworkUpdate;
|
||||
this.Functions.SetChatInteractable(true);
|
||||
|
||||
this.Ui.Dispose();
|
||||
this.Store.Dispose();
|
||||
this.Functions.Dispose();
|
||||
this.Common.Dispose();
|
||||
}
|
||||
|
||||
internal void SaveConfig() {
|
||||
this.Interface.SavePluginConfig(this.Config);
|
||||
}
|
||||
|
||||
private static readonly string[] ChatAddonNames = {
|
||||
"ChatLog",
|
||||
"ChatLogPanel_0",
|
||||
"ChatLogPanel_1",
|
||||
"ChatLogPanel_2",
|
||||
"ChatLogPanel_3",
|
||||
};
|
||||
|
||||
private void FrameworkUpdate(Framework framework) {
|
||||
if (!this.Config.HideChat) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var name in ChatAddonNames) {
|
||||
if (GameFunctions.IsAddonInteractable(name)) {
|
||||
GameFunctions.SetAddonInteractable(name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+187
@@ -0,0 +1,187 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using ChatTwo.Ui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
internal sealed class PluginUi : IDisposable {
|
||||
internal Plugin Plugin { get; }
|
||||
internal ImFontPtr? RegularFont { get; private set; }
|
||||
internal ImFontPtr? ItalicFont { get; private set; }
|
||||
|
||||
private List<IUiComponent> Components { get; }
|
||||
private ImFontConfigPtr _fontCfg;
|
||||
private ImFontConfigPtr _fontCfgMerge;
|
||||
private (GCHandle, int) _regularFont;
|
||||
private (GCHandle, int) _italicFont;
|
||||
private (GCHandle, int) _jpFont;
|
||||
private (GCHandle, int) _gameSymFont;
|
||||
|
||||
private ImVector _ranges;
|
||||
|
||||
private GCHandle _jpRange = GCHandle.Alloc(
|
||||
GlyphRangesJapanese.GlyphRanges,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
|
||||
private GCHandle _symRange = GCHandle.Alloc(
|
||||
new ushort[] {
|
||||
0xE020,
|
||||
0xE0DB,
|
||||
0,
|
||||
},
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
|
||||
internal unsafe PluginUi(Plugin plugin) {
|
||||
this.Plugin = plugin;
|
||||
this.Components = new List<IUiComponent> {
|
||||
new Settings(this),
|
||||
new ChatLog(this),
|
||||
};
|
||||
|
||||
this._fontCfg = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig()) {
|
||||
FontDataOwnedByAtlas = false,
|
||||
};
|
||||
|
||||
this._fontCfgMerge = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig()) {
|
||||
FontDataOwnedByAtlas = false,
|
||||
MergeMode = true,
|
||||
};
|
||||
|
||||
var builder = new ImFontGlyphRangesBuilderPtr(ImGuiNative.ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder());
|
||||
builder.AddRanges(ImGui.GetIO().Fonts.GetGlyphRangesDefault());
|
||||
builder.AddText("←→↑↓《》■※☀★★☆♥♡ヅツッシ☀☁☂℃℉°♀♂♠♣♦♣♧®©™€$£♯♭♪✓√◎◆◇♦■□〇●△▽▼▲‹›≤≥<«“”─");
|
||||
builder.BuildRanges(out this._ranges);
|
||||
|
||||
var regular = this.GetResource("ChatTwo.fonts.NotoSans-Regular.ttf");
|
||||
this._regularFont = (
|
||||
GCHandle.Alloc(regular, GCHandleType.Pinned),
|
||||
regular.Length
|
||||
);
|
||||
|
||||
var italic = this.GetResource("ChatTwo.fonts.NotoSans-Italic.ttf");
|
||||
this._italicFont = (
|
||||
GCHandle.Alloc(italic, GCHandleType.Pinned),
|
||||
italic.Length
|
||||
);
|
||||
|
||||
var jp = this.GetResource("ChatTwo.fonts.NotoSansJP-Regular.otf");
|
||||
this._jpFont = (
|
||||
GCHandle.Alloc(jp, GCHandleType.Pinned),
|
||||
jp.Length
|
||||
);
|
||||
|
||||
var gameSym = File.ReadAllBytes(Path.Combine(this.Plugin.Interface.DalamudAssetDirectory.FullName, "UIRes", "gamesym.ttf"));
|
||||
this._gameSymFont = (
|
||||
GCHandle.Alloc(gameSym, GCHandleType.Pinned),
|
||||
gameSym.Length
|
||||
);
|
||||
|
||||
this.Plugin.Interface.UiBuilder.BuildFonts += this.BuildFonts;
|
||||
this.Plugin.Interface.UiBuilder.Draw += this.Draw;
|
||||
|
||||
this.Plugin.Interface.UiBuilder.RebuildFonts();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Plugin.Interface.UiBuilder.Draw -= this.Draw;
|
||||
this.Plugin.Interface.UiBuilder.BuildFonts -= this.BuildFonts;
|
||||
|
||||
foreach (var component in this.Components) {
|
||||
component.Dispose();
|
||||
}
|
||||
|
||||
this._regularFont.Item1.Free();
|
||||
this._italicFont.Item1.Free();
|
||||
this._gameSymFont.Item1.Free();
|
||||
this._symRange.Free();
|
||||
this._jpRange.Free();
|
||||
this._fontCfg.Destroy();
|
||||
this._fontCfgMerge.Destroy();
|
||||
}
|
||||
|
||||
private void Draw() {
|
||||
var font = this.RegularFont.HasValue;
|
||||
|
||||
if (font) {
|
||||
ImGui.PushFont(this.RegularFont!.Value);
|
||||
}
|
||||
|
||||
foreach (var component in this.Components) {
|
||||
try {
|
||||
component.Draw();
|
||||
} catch (Exception ex) {
|
||||
PluginLog.LogError(ex, "Error drawing component");
|
||||
}
|
||||
}
|
||||
|
||||
if (font) {
|
||||
ImGui.PopFont();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetResource(string name) {
|
||||
var stream = this.GetType().Assembly.GetManifestResourceStream(name)!;
|
||||
var memory = new MemoryStream();
|
||||
stream.CopyTo(memory);
|
||||
return memory.ToArray();
|
||||
}
|
||||
|
||||
private void BuildFonts() {
|
||||
this.RegularFont = null;
|
||||
this.ItalicFont = null;
|
||||
|
||||
// load regular noto sans and merge in jp + game icons
|
||||
this.RegularFont = ImGui.GetIO().Fonts.AddFontFromMemoryTTF(
|
||||
this._regularFont.Item1.AddrOfPinnedObject(),
|
||||
this._regularFont.Item2,
|
||||
this.Plugin.Config.FontSize,
|
||||
this._fontCfg,
|
||||
this._ranges.Data
|
||||
);
|
||||
|
||||
ImGui.GetIO().Fonts.AddFontFromMemoryTTF(
|
||||
this._jpFont.Item1.AddrOfPinnedObject(),
|
||||
this._jpFont.Item2,
|
||||
this.Plugin.Config.FontSize,
|
||||
this._fontCfgMerge,
|
||||
this._jpRange.AddrOfPinnedObject()
|
||||
);
|
||||
|
||||
ImGui.GetIO().Fonts.AddFontFromMemoryTTF(
|
||||
this._gameSymFont.Item1.AddrOfPinnedObject(),
|
||||
this._gameSymFont.Item2,
|
||||
this.Plugin.Config.FontSize,
|
||||
this._fontCfgMerge,
|
||||
this._symRange.AddrOfPinnedObject()
|
||||
);
|
||||
|
||||
// load italic noto sans and merge in jp + game icons
|
||||
this.ItalicFont = ImGui.GetIO().Fonts.AddFontFromMemoryTTF(
|
||||
this._italicFont.Item1.AddrOfPinnedObject(),
|
||||
this._italicFont.Item2,
|
||||
this.Plugin.Config.FontSize,
|
||||
this._fontCfg,
|
||||
this._ranges.Data
|
||||
);
|
||||
|
||||
ImGui.GetIO().Fonts.AddFontFromMemoryTTF(
|
||||
this._jpFont.Item1.AddrOfPinnedObject(),
|
||||
this._jpFont.Item2,
|
||||
this.Plugin.Config.FontSize,
|
||||
this._fontCfgMerge,
|
||||
this._jpRange.AddrOfPinnedObject()
|
||||
);
|
||||
|
||||
ImGui.GetIO().Fonts.AddFontFromMemoryTTF(
|
||||
this._gameSymFont.Item1.AddrOfPinnedObject(),
|
||||
this._gameSymFont.Item2,
|
||||
this.Plugin.Config.FontSize,
|
||||
this._fontCfgMerge,
|
||||
this._symRange.AddrOfPinnedObject()
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+168
@@ -0,0 +1,168 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
internal class Store : IDisposable {
|
||||
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 Dictionary<ChatType, NameFormatting> Formats { get; } = new();
|
||||
|
||||
internal Store(Plugin plugin) {
|
||||
this.Plugin = plugin;
|
||||
|
||||
this.Plugin.ChatGui.ChatMessageUnhandled += this.ChatMessage;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Plugin.ChatGui.ChatMessageUnhandled -= this.ChatMessage;
|
||||
|
||||
this.MessagesMutex.Dispose();
|
||||
}
|
||||
|
||||
internal MessagesLock GetMessages() {
|
||||
return new MessagesLock(this.Messages, this.MessagesMutex);
|
||||
}
|
||||
|
||||
internal void AddMessage(Message message) {
|
||||
using var messages = this.GetMessages();
|
||||
messages.Messages.Add(message);
|
||||
|
||||
if (messages.Messages.Count > 1_000) {
|
||||
messages.Messages.RemoveAt(0);
|
||||
}
|
||||
|
||||
foreach (var tab in this.Plugin.Config.Tabs) {
|
||||
if (tab.Matches(message)) {
|
||||
tab.AddMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void FilterAllTabs() {
|
||||
foreach (var tab in this.Plugin.Config.Tabs) {
|
||||
this.FilterTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
internal void FilterTab(Tab tab) {
|
||||
using var messages = this.GetMessages();
|
||||
foreach (var message in messages.Messages) {
|
||||
if (tab.Matches(message)) {
|
||||
tab.AddMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ChatMessage(XivChatType type, uint senderId, SeString sender, SeString message) {
|
||||
var chatCode = new ChatCode((ushort) type);
|
||||
|
||||
NameFormatting? formatting = null;
|
||||
if (sender.Payloads.Count > 0) {
|
||||
formatting = this.FormatFor(chatCode.Type);
|
||||
}
|
||||
|
||||
var senderChunks = new List<Chunk>();
|
||||
if (formatting is { IsPresent: true }) {
|
||||
senderChunks.Add(new TextChunk(formatting.Before) {
|
||||
FallbackColour = chatCode.Type,
|
||||
});
|
||||
senderChunks.AddRange(ChunkUtil.ToChunks(sender, chatCode.Type));
|
||||
senderChunks.Add(new TextChunk(formatting.After) {
|
||||
FallbackColour = chatCode.Type,
|
||||
});
|
||||
}
|
||||
|
||||
var messageChunks = ChunkUtil.ToChunks(message, chatCode.Type).ToList();
|
||||
|
||||
this.AddMessage(new Message(chatCode, senderChunks, messageChunks));
|
||||
}
|
||||
|
||||
internal class NameFormatting {
|
||||
internal string Before { get; private set; } = string.Empty;
|
||||
internal string After { get; private set; } = string.Empty;
|
||||
internal bool IsPresent { get; private set; } = true;
|
||||
|
||||
internal static NameFormatting Empty() {
|
||||
return new() {
|
||||
IsPresent = false,
|
||||
};
|
||||
}
|
||||
|
||||
internal static NameFormatting Of(string before, string after) {
|
||||
return new() {
|
||||
Before = before,
|
||||
After = after,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private NameFormatting? FormatFor(ChatType type) {
|
||||
if (this.Formats.TryGetValue(type, out var cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
var logKind = this.Plugin.DataManager.GetExcelSheet<LogKind>()!.GetRow((ushort) type);
|
||||
|
||||
if (logKind == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var format = (SeString) logKind.Format;
|
||||
|
||||
static bool IsStringParam(Payload payload, byte num) {
|
||||
var data = payload.Encode();
|
||||
|
||||
return data.Length >= 5 && data[1] == 0x29 && data[4] == num + 1;
|
||||
}
|
||||
|
||||
var firstStringParam = format.Payloads.FindIndex(payload => IsStringParam(payload, 1));
|
||||
var secondStringParam = format.Payloads.FindIndex(payload => IsStringParam(payload, 2));
|
||||
|
||||
if (firstStringParam == -1 || secondStringParam == -1) {
|
||||
return NameFormatting.Empty();
|
||||
}
|
||||
|
||||
var before = format.Payloads
|
||||
.GetRange(0, firstStringParam)
|
||||
.Where(payload => payload is ITextProvider)
|
||||
.Cast<ITextProvider>()
|
||||
.Select(text => text.Text);
|
||||
var after = format.Payloads
|
||||
.GetRange(firstStringParam + 1, secondStringParam - firstStringParam)
|
||||
.Where(payload => payload is ITextProvider)
|
||||
.Cast<ITextProvider>()
|
||||
.Select(text => text.Text);
|
||||
|
||||
var nameFormatting = NameFormatting.Of(
|
||||
string.Join("", before),
|
||||
string.Join("", after)
|
||||
);
|
||||
|
||||
this.Formats[type] = nameFormatting;
|
||||
|
||||
return nameFormatting;
|
||||
}
|
||||
}
|
||||
Executable
+290
@@ -0,0 +1,290 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
internal sealed class ChatLog : IUiComponent {
|
||||
private PluginUi Ui { get; }
|
||||
|
||||
private bool _activate;
|
||||
private string _chat = string.Empty;
|
||||
private readonly TextureWrap? _fontIcon;
|
||||
private readonly List<string> _inputBacklog = new();
|
||||
private int _inputBacklogIdx = -1;
|
||||
private int _lastTab;
|
||||
|
||||
internal ChatLog(PluginUi ui) {
|
||||
this.Ui = ui;
|
||||
|
||||
this._fontIcon = this.Ui.Plugin.DataManager.GetImGuiTexture("common/font/fonticon_ps5.tex");
|
||||
|
||||
this.Ui.Plugin.Functions.ChatActivated += this.ChatActivated;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Ui.Plugin.Functions.ChatActivated -= this.ChatActivated;
|
||||
this._fontIcon?.Dispose();
|
||||
}
|
||||
|
||||
private void ChatActivated(string? input) {
|
||||
this._activate = true;
|
||||
if (input != null && !this._chat.Contains(input)) {
|
||||
this._chat += input;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddBacklog(string message) {
|
||||
for (var i = 0; i < this._inputBacklog.Count; i++) {
|
||||
if (this._inputBacklog[i] != message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._inputBacklog.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
|
||||
this._inputBacklog.Add(message);
|
||||
}
|
||||
|
||||
public unsafe void Draw() {
|
||||
if (!ImGui.Begin($"{this.Ui.Plugin.Name}##chat", ImGuiWindowFlags.NoTitleBar)) {
|
||||
ImGui.End();
|
||||
return;
|
||||
}
|
||||
|
||||
var lineHeight = ImGui.CalcTextSize("A").Y;
|
||||
|
||||
if (ImGui.BeginTabBar("##chat2-tabs")) {
|
||||
for (var tabI = 0; tabI < this.Ui.Plugin.Config.Tabs.Count; tabI++) {
|
||||
var tab = this.Ui.Plugin.Config.Tabs[tabI];
|
||||
|
||||
var unread = tabI == this._lastTab || !tab.DisplayUnread || tab.Unread == 0 ? "" : $" ({tab.Unread})";
|
||||
if (ImGui.BeginTabItem($"{tab.Name}{unread}###log-tab-{tabI}")) {
|
||||
var switchedTab = this._lastTab != tabI;
|
||||
this._lastTab = tabI;
|
||||
tab.Unread = 0;
|
||||
|
||||
// var drawnHeight = 0f;
|
||||
// var numDrawn = 0;
|
||||
// var lastPos = ImGui.GetCursorPosY();
|
||||
var height = ImGui.GetContentRegionAvail().Y
|
||||
- lineHeight * 2
|
||||
- ImGui.GetStyle().ItemSpacing.Y * 4;
|
||||
if (ImGui.BeginChild("##chat2-messages", new Vector2(-1, height))) {
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
|
||||
// var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||
// int numMessages;
|
||||
try {
|
||||
tab.MessagesMutex.WaitOne();
|
||||
|
||||
for (var i = 0; i < tab.Messages.Count; i++) {
|
||||
// numDrawn += 1;
|
||||
var message = tab.Messages[i];
|
||||
|
||||
if (tab.DisplayTimestamp) {
|
||||
var timestamp = message.Date.ToLocalTime().ToString("t");
|
||||
this.DrawChunk(new TextChunk($"[{timestamp}]") {
|
||||
Foreground = 0xFFFFFFFF,
|
||||
});
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (message.Sender.Count > 0) {
|
||||
this.DrawChunks(message.Sender);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
this.DrawChunks(message.Content);
|
||||
|
||||
// drawnHeight += ImGui.GetCursorPosY() - lastPos;
|
||||
// lastPos = ImGui.GetCursorPosY();
|
||||
}
|
||||
|
||||
// numMessages = tab.Messages.Count;
|
||||
// may render too many items, but this is easier
|
||||
// clipper.Begin(numMessages, lineHeight + ImGui.GetStyle().ItemSpacing.Y);
|
||||
// while (clipper.Step()) {
|
||||
// }
|
||||
} finally {
|
||||
tab.MessagesMutex.ReleaseMutex();
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
// PluginLog.Log($"numDrawn: {numDrawn}");
|
||||
|
||||
if (switchedTab || ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) {
|
||||
// PluginLog.Log($"drawnHeight: {drawnHeight}");
|
||||
// var itemPosY = clipper.StartPosY + drawnHeight;
|
||||
// PluginLog.Log($"itemPosY: {itemPosY}");
|
||||
// ImGui.SetScrollFromPosY(itemPosY - ImGui.GetWindowPos().Y);
|
||||
ImGui.SetScrollHereY(1f);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTabBar();
|
||||
}
|
||||
|
||||
if (this._activate) {
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(this.Ui.Plugin.Functions.ChatChannel.name);
|
||||
|
||||
var inputType = this.Ui.Plugin.Functions.ChatChannel.channel.ToChatType();
|
||||
var inputColour = this.Ui.Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol)
|
||||
? inputCol
|
||||
: inputType.DefaultColour();
|
||||
|
||||
if (inputColour != null) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(inputColour.Value));
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
const ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags.EnterReturnsTrue
|
||||
| ImGuiInputTextFlags.CallbackAlways
|
||||
| ImGuiInputTextFlags.CallbackHistory;
|
||||
if (ImGui.InputText("##chat2-input", ref this._chat, 500, inputFlags, this.Callback)) {
|
||||
if (!string.IsNullOrWhiteSpace(this._chat)) {
|
||||
var trimmed = this._chat.Trim();
|
||||
this.AddBacklog(trimmed);
|
||||
this._inputBacklogIdx = -1;
|
||||
|
||||
this.Ui.Plugin.Common.Functions.Chat.SendMessage(trimmed);
|
||||
}
|
||||
|
||||
this._chat = string.Empty;
|
||||
}
|
||||
|
||||
if (inputColour != null) {
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
|
||||
private unsafe int Callback(ImGuiInputTextCallbackData* data) {
|
||||
var ptr = new ImGuiInputTextCallbackDataPtr(data);
|
||||
|
||||
if (this._activate) {
|
||||
this._activate = false;
|
||||
data->CursorPos = this._chat.Length;
|
||||
data->SelectionStart = data->SelectionEnd = data->CursorPos;
|
||||
}
|
||||
|
||||
if (data->EventFlag != ImGuiInputTextFlags.CallbackHistory) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var prevPos = this._inputBacklogIdx;
|
||||
|
||||
switch (data->EventKey) {
|
||||
case ImGuiKey.UpArrow:
|
||||
switch (this._inputBacklogIdx) {
|
||||
case -1:
|
||||
var offset = 0;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this._chat)) {
|
||||
this.AddBacklog(this._chat);
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
this._inputBacklogIdx = this._inputBacklog.Count - 1 - offset;
|
||||
break;
|
||||
case > 0:
|
||||
this._inputBacklogIdx--;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case ImGuiKey.DownArrow: {
|
||||
if (this._inputBacklogIdx != -1) {
|
||||
if (++this._inputBacklogIdx >= this._inputBacklog.Count) {
|
||||
this._inputBacklogIdx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevPos == this._inputBacklogIdx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var historyStr = this._inputBacklogIdx >= 0 ? this._inputBacklog[this._inputBacklogIdx] : string.Empty;
|
||||
|
||||
ptr.DeleteChars(0, ptr.BufTextLen);
|
||||
ptr.InsertChars(0, historyStr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void DrawChunks(IReadOnlyList<Chunk> chunks) {
|
||||
for (var i = 0; i < chunks.Count; i++) {
|
||||
this.DrawChunk(chunks[i]);
|
||||
|
||||
if (i < chunks.Count - 1) {
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChunk(Chunk chunk) {
|
||||
if (chunk is IconChunk icon && this._fontIcon != null) {
|
||||
var bounds = IconUtil.GetBounds((byte) icon.Icon);
|
||||
if (bounds != null) {
|
||||
var texSize = new Vector2(this._fontIcon.Width, this._fontIcon.Height);
|
||||
|
||||
var sizeRatio = this.Ui.Plugin.Config.FontSize / bounds.Value.W;
|
||||
var size = new Vector2(bounds.Value.Z, bounds.Value.W) * sizeRatio;
|
||||
|
||||
var uv0 = new Vector2(bounds.Value.X, bounds.Value.Y - 2) / texSize;
|
||||
var uv1 = new Vector2(bounds.Value.X + bounds.Value.Z, bounds.Value.Y - 2 + bounds.Value.W) / texSize;
|
||||
ImGui.Image(this._fontIcon.ImGuiHandle, size, uv0, uv1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk is not TextChunk text) {
|
||||
return;
|
||||
}
|
||||
|
||||
var colour = text.Foreground;
|
||||
if (colour == null && text.FallbackColour != null) {
|
||||
var type = text.FallbackColour.Value;
|
||||
colour = this.Ui.Plugin.Config.ChatColours.TryGetValue(type, out var col)
|
||||
? col
|
||||
: type.DefaultColour();
|
||||
}
|
||||
|
||||
if (colour != null) {
|
||||
colour = ColourUtil.RgbaToAbgr(colour.Value);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, colour.Value);
|
||||
}
|
||||
|
||||
if (text.Italic && this.Ui.ItalicFont.HasValue) {
|
||||
ImGui.PushFont(this.Ui.ItalicFont.Value);
|
||||
}
|
||||
|
||||
ImGuiUtil.WrapText(text.Content);
|
||||
|
||||
if (text.Italic && this.Ui.ItalicFont.HasValue) {
|
||||
ImGui.PopFont();
|
||||
}
|
||||
|
||||
if (colour != null) {
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+185
@@ -0,0 +1,185 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Command;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
internal sealed class Settings : IUiComponent {
|
||||
private PluginUi Ui { get; }
|
||||
|
||||
private bool _visible;
|
||||
|
||||
private bool _hideChat;
|
||||
private float _fontSize;
|
||||
private Dictionary<ChatType, uint> _chatColours = new();
|
||||
private List<Tab> _tabs = new();
|
||||
|
||||
internal Settings(PluginUi ui) {
|
||||
this.Ui = ui;
|
||||
this.Ui.Plugin.CommandManager.AddHandler("/chat2", new CommandInfo(this.Command) {
|
||||
HelpMessage = "Toggle the Chat 2 settings",
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Ui.Plugin.CommandManager.RemoveHandler("/chat2");
|
||||
}
|
||||
|
||||
private void Command(string command, string args) {
|
||||
this._visible ^= true;
|
||||
}
|
||||
|
||||
private void Initialise() {
|
||||
var config = this.Ui.Plugin.Config;
|
||||
this._hideChat = config.HideChat;
|
||||
this._fontSize = config.FontSize;
|
||||
this._chatColours = config.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value);
|
||||
this._tabs = config.Tabs.Select(tab => tab.Clone()).ToList();
|
||||
}
|
||||
|
||||
public void Draw() {
|
||||
if (!this._visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui.Begin($"{this.Ui.Plugin.Name} settings", ref this._visible)) {
|
||||
ImGui.End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this.Initialise();
|
||||
}
|
||||
|
||||
var height = ImGui.GetContentRegionAvail().Y
|
||||
- ImGui.GetStyle().FramePadding.Y * 2
|
||||
- ImGui.GetStyle().ItemSpacing.Y
|
||||
- ImGui.GetStyle().ItemInnerSpacing.Y * 2
|
||||
- ImGui.CalcTextSize("A").Y;
|
||||
if (ImGui.BeginChild("##chat2-settings", new Vector2(-1, height))) {
|
||||
ImGui.Checkbox("Hide chat", ref this._hideChat);
|
||||
ImGui.DragFloat("Font size", ref this._fontSize, .5f, 12f, 36f);
|
||||
|
||||
if (ImGui.TreeNodeEx("Chat colours")) {
|
||||
foreach (var type in Enum.GetValues<ChatType>()) {
|
||||
if (ImGui.Button($"Default##{type}")) {
|
||||
this._chatColours.Remove(type);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var vec = this._chatColours.TryGetValue(type, out var colour)
|
||||
? ColourUtil.RgbaToVector3(colour)
|
||||
: ColourUtil.RgbaToVector3(type.DefaultColour() ?? 0);
|
||||
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs)) {
|
||||
this._chatColours[type] = ColourUtil.Vector3ToRgba(vec);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
if (ImGui.TreeNodeEx("Tabs")) {
|
||||
if (ImGui.Button("Add")) {
|
||||
this._tabs.Add(new Tab());
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._tabs.Count; i++) {
|
||||
var tab = this._tabs[i];
|
||||
|
||||
if (ImGui.TreeNodeEx($"{tab.Name}###tab-{i}")) {
|
||||
ImGui.PushID($"tab-{i}");
|
||||
|
||||
ImGui.InputText("Name", ref tab.Name, 512, ImGuiInputTextFlags.EnterReturnsTrue);
|
||||
ImGui.Checkbox("Show unread count", ref tab.DisplayUnread);
|
||||
ImGui.Checkbox("Show timestamps", ref tab.DisplayTimestamp);
|
||||
|
||||
if (ImGui.TreeNodeEx("Channels")) {
|
||||
foreach (var type in Enum.GetValues<ChatType>()) {
|
||||
var enabled = tab.ChatCodes.ContainsKey(type);
|
||||
if (ImGui.Checkbox($"##{type.Name()}-{i}", ref enabled)) {
|
||||
if (enabled) {
|
||||
tab.ChatCodes[type] = ChatSourceExt.All;
|
||||
} else {
|
||||
tab.ChatCodes.Remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.TreeNodeEx($"{type.Name()}##{i}")) {
|
||||
tab.ChatCodes.TryGetValue(type, out var sourcesEnum);
|
||||
var sources = (uint) sourcesEnum;
|
||||
|
||||
foreach (var source in Enum.GetValues<ChatSource>()) {
|
||||
if (ImGui.CheckboxFlags(source.ToString(), ref sources, (uint) source)) {
|
||||
tab.ChatCodes[type] = (ChatSource) sources;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var save = ImGui.Button("Save");
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Save and close")) {
|
||||
save = true;
|
||||
this._visible = false;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Discard")) {
|
||||
this._visible = false;
|
||||
}
|
||||
|
||||
ImGui.End();
|
||||
|
||||
if (save) {
|
||||
var config = this.Ui.Plugin.Config;
|
||||
|
||||
var hideChatChanged = this._hideChat != this.Ui.Plugin.Config.HideChat;
|
||||
var fontSizeChanged = Math.Abs(this._fontSize - this.Ui.Plugin.Config.FontSize) > float.Epsilon;
|
||||
|
||||
config.HideChat = this._hideChat;
|
||||
config.FontSize = this._fontSize;
|
||||
config.ChatColours = this._chatColours;
|
||||
config.Tabs = this._tabs;
|
||||
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
|
||||
this.Ui.Plugin.Store.FilterAllTabs();
|
||||
|
||||
if (fontSizeChanged) {
|
||||
this.Ui.Plugin.Interface.UiBuilder.RebuildFonts();
|
||||
}
|
||||
|
||||
if (!this._hideChat && hideChatChanged) {
|
||||
this.Ui.Plugin.Functions.SetChatInteractable(true);
|
||||
}
|
||||
|
||||
this.Initialise();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
internal interface IUiComponent : IDisposable {
|
||||
void Draw();
|
||||
}
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
using ChatTwo.Code;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
namespace ChatTwo.Util;
|
||||
|
||||
internal static class ChunkUtil {
|
||||
internal static IEnumerable<Chunk> ToChunks(SeString msg, ChatType? defaultColour) {
|
||||
var chunks = new List<Chunk>();
|
||||
|
||||
var italic = false;
|
||||
var foreground = new Stack<uint>();
|
||||
var glow = new Stack<uint>();
|
||||
|
||||
void Append(string text) {
|
||||
chunks.Add(new TextChunk(text) {
|
||||
FallbackColour = defaultColour,
|
||||
Foreground = foreground.Count > 0 ? foreground.Peek() : null,
|
||||
Glow = glow.Count > 0 ? glow.Peek() : null,
|
||||
Italic = italic,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var payload in msg.Payloads) {
|
||||
switch (payload.Type) {
|
||||
case PayloadType.EmphasisItalic:
|
||||
var newStatus = ((EmphasisItalicPayload) payload).IsEnabled;
|
||||
italic = newStatus;
|
||||
break;
|
||||
case PayloadType.UIForeground:
|
||||
var foregroundPayload = (UIForegroundPayload) payload;
|
||||
if (foregroundPayload.IsEnabled) {
|
||||
foreground.Push(foregroundPayload.UIColor.UIForeground);
|
||||
} else if (foreground.Count > 0) {
|
||||
foreground.Pop();
|
||||
}
|
||||
|
||||
break;
|
||||
case PayloadType.UIGlow:
|
||||
var glowPayload = (UIGlowPayload) payload;
|
||||
if (glowPayload.IsEnabled) {
|
||||
glow.Push(glowPayload.UIColor.UIGlow);
|
||||
} else if (glow.Count > 0) {
|
||||
glow.Pop();
|
||||
}
|
||||
|
||||
break;
|
||||
case PayloadType.AutoTranslateText:
|
||||
chunks.Add(new IconChunk(BitmapFontIcon.AutoTranslateBegin));
|
||||
var autoText = ((AutoTranslatePayload) payload).Text;
|
||||
Append(autoText.Substring(2, autoText.Length - 4));
|
||||
chunks.Add(new IconChunk(BitmapFontIcon.AutoTranslateEnd));
|
||||
break;
|
||||
case PayloadType.Icon:
|
||||
chunks.Add(new IconChunk(((IconPayload) payload).Icon));
|
||||
break;
|
||||
case PayloadType.Unknown:
|
||||
var rawPayload = (RawPayload) payload;
|
||||
if (rawPayload.Data[1] == 0x13) {
|
||||
foreground.Pop();
|
||||
glow.Pop();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
if (payload is ITextProvider textProvider) {
|
||||
Append(textProvider.Text);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
}
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace ChatTwo.Util;
|
||||
|
||||
internal static class ColourUtil {
|
||||
private static (byte r, byte g, byte b, byte a) RgbaToComponents(uint rgba) {
|
||||
var r = (byte) ((rgba & 0xFF000000) >> 24);
|
||||
var g = (byte) ((rgba & 0xFF0000) >> 16);
|
||||
var b = (byte) ((rgba & 0xFF00) >> 8);
|
||||
var a = (byte) (rgba & 0xFF);
|
||||
return (r, g, b, a);
|
||||
}
|
||||
|
||||
internal static uint RgbaToAbgr(uint rgba) {
|
||||
var (r, g, b, a) = RgbaToComponents(rgba);
|
||||
return (uint) ((a << 24) | (b << 16) | (g << 8) | r);
|
||||
}
|
||||
|
||||
internal static Vector3 RgbaToVector3(uint rgba) {
|
||||
var (r, g, b, _) = RgbaToComponents(rgba);
|
||||
return new Vector3((float) r / 255, (float) g / 255, (float) b / 255);
|
||||
}
|
||||
|
||||
internal static uint Vector3ToRgba(Vector3 col) {
|
||||
return ComponentsToRgba(
|
||||
(byte) Math.Round(col.X * 255),
|
||||
(byte) Math.Round(col.Y * 255),
|
||||
(byte) Math.Round(col.Z * 255)
|
||||
);
|
||||
}
|
||||
|
||||
internal static uint ComponentsToRgba(byte red, byte green, byte blue, byte alpha = 0xFF) => alpha
|
||||
| (uint) (red << 24)
|
||||
| (uint) (green << 16)
|
||||
| (uint) (blue << 8);
|
||||
}
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace ChatTwo.Util;
|
||||
|
||||
internal static class IconUtil {
|
||||
internal static Vector4? GetBounds(byte id) => id switch {
|
||||
1 => new Vector4(0, 342, 40, 40),
|
||||
2 => new Vector4(40, 342, 40, 40),
|
||||
3 => new Vector4(80, 342, 40, 40),
|
||||
4 => new Vector4(120, 342, 40, 40),
|
||||
5 => new Vector4(160, 342, 40, 40),
|
||||
6 => new Vector4(0, 382, 40, 40),
|
||||
7 => new Vector4(40, 382, 40, 40),
|
||||
8 => new Vector4(80, 382, 40, 40),
|
||||
9 => new Vector4(120, 382, 40, 40),
|
||||
10 => new Vector4(160, 382, 40, 40),
|
||||
11 => new Vector4(0, 422, 40, 40),
|
||||
12 => new Vector4(40, 422, 40, 40),
|
||||
13 => new Vector4(80, 422, 40, 40),
|
||||
14 => new Vector4(120, 422, 40, 40),
|
||||
15 => new Vector4(160, 422, 40, 40),
|
||||
16 => new Vector4(120, 542, 40, 40),
|
||||
17 => new Vector4(160, 542, 40, 40),
|
||||
18 => new Vector4(0, 462, 108, 40),
|
||||
19 => new Vector4(108, 462, 108, 40),
|
||||
20 => new Vector4(120, 502, 40, 40),
|
||||
21 => new Vector4(0, 502, 56, 40),
|
||||
22 => new Vector4(56, 502, 64, 40),
|
||||
23 => new Vector4(160, 502, 40, 40),
|
||||
24 => new Vector4(0, 542, 56, 40),
|
||||
25 => new Vector4(56, 542, 64, 40),
|
||||
51 => new Vector4(248, 342, 40, 40),
|
||||
52 => new Vector4(288, 342, 40, 40),
|
||||
53 => new Vector4(328, 342, 40, 40),
|
||||
54 => new Vector4(200, 342, 24, 40),
|
||||
55 => new Vector4(224, 342, 24, 40),
|
||||
56 => new Vector4(200, 382, 40, 40),
|
||||
57 => new Vector4(240, 382, 40, 40),
|
||||
58 => new Vector4(280, 382, 40, 40),
|
||||
59 => new Vector4(200, 422, 40, 40),
|
||||
60 => new Vector4(240, 422, 40, 40),
|
||||
61 => new Vector4(280, 422, 40, 40),
|
||||
62 => new Vector4(320, 382, 40, 40),
|
||||
63 => new Vector4(320, 422, 40, 40),
|
||||
64 => new Vector4(368, 342, 40, 40),
|
||||
65 => new Vector4(408, 342, 40, 40),
|
||||
66 => new Vector4(448, 342, 40, 40),
|
||||
67 => new Vector4(360, 382, 40, 40),
|
||||
68 => new Vector4(400, 382, 40, 40),
|
||||
70 => new Vector4(360, 422, 40, 40),
|
||||
71 => new Vector4(400, 422, 40, 40),
|
||||
72 => new Vector4(440, 422, 40, 40),
|
||||
73 => new Vector4(440, 382, 40, 40),
|
||||
74 => new Vector4(216, 462, 40, 40),
|
||||
75 => new Vector4(256, 462, 40, 40),
|
||||
76 => new Vector4(296, 462, 40, 40),
|
||||
77 => new Vector4(336, 462, 40, 40),
|
||||
78 => new Vector4(376, 462, 40, 40),
|
||||
79 => new Vector4(416, 462, 40, 40),
|
||||
80 => new Vector4(456, 462, 40, 40),
|
||||
81 => new Vector4(200, 502, 40, 40),
|
||||
82 => new Vector4(240, 502, 40, 40),
|
||||
83 => new Vector4(280, 502, 40, 40),
|
||||
84 => new Vector4(320, 502, 40, 40),
|
||||
85 => new Vector4(360, 502, 40, 40),
|
||||
86 => new Vector4(400, 502, 40, 40),
|
||||
87 => new Vector4(440, 502, 40, 40),
|
||||
88 => new Vector4(200, 542, 40, 40),
|
||||
89 => new Vector4(240, 542, 40, 40),
|
||||
90 => new Vector4(280, 542, 40, 40),
|
||||
91 => new Vector4(320, 542, 40, 40),
|
||||
92 => new Vector4(360, 542, 40, 40),
|
||||
93 => new Vector4(400, 542, 40, 40),
|
||||
94 => new Vector4(440, 542, 40, 40),
|
||||
95 => new Vector4(0, 582, 40, 40),
|
||||
96 => new Vector4(40, 582, 40, 40),
|
||||
97 => new Vector4(80, 582, 40, 40),
|
||||
98 => new Vector4(120, 582, 40, 40),
|
||||
99 => new Vector4(160, 582, 40, 40),
|
||||
100 => new Vector4(200, 582, 40, 40),
|
||||
101 => new Vector4(240, 582, 40, 40),
|
||||
102 => new Vector4(280, 582, 40, 40),
|
||||
103 => new Vector4(320, 582, 40, 40),
|
||||
104 => new Vector4(360, 582, 40, 40),
|
||||
105 => new Vector4(400, 582, 40, 40),
|
||||
106 => new Vector4(440, 582, 40, 40),
|
||||
107 => new Vector4(0, 622, 40, 40),
|
||||
108 => new Vector4(40, 622, 40, 40),
|
||||
109 => new Vector4(80, 622, 40, 40),
|
||||
110 => new Vector4(120, 622, 40, 40),
|
||||
111 => new Vector4(160, 622, 40, 40),
|
||||
112 => new Vector4(200, 622, 40, 40),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
using System.Text;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace ChatTwo.Util;
|
||||
|
||||
internal static class ImGuiUtil {
|
||||
internal static unsafe void WrapText(string csText) {
|
||||
foreach (var part in csText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) {
|
||||
var bytes = Encoding.UTF8.GetBytes(part);
|
||||
fixed (byte* rawText = bytes) {
|
||||
var text = rawText;
|
||||
var textEnd = text + bytes.Length;
|
||||
|
||||
// idk how this is possible, but it is, I guess
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float scale = 1.0f;
|
||||
var widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
var endPrevLine = ImGuiNative.ImFont_CalcWordWrapPositionA(ImGui.GetFont().NativePtr, scale, text, textEnd, widthLeft);
|
||||
if (endPrevLine == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiNative.igTextUnformatted(text, endPrevLine);
|
||||
|
||||
widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
while (endPrevLine < textEnd) {
|
||||
text = endPrevLine;
|
||||
if (*text == ' ') {
|
||||
++text;
|
||||
} // skip a space at start of line
|
||||
|
||||
endPrevLine = ImGuiNative.ImFont_CalcWordWrapPositionA(ImGui.GetFont().NativePtr, scale, text, textEnd, widthLeft);
|
||||
if (endPrevLine == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
ImGuiNative.igTextUnformatted(text, endPrevLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user