chore: initial commit
This commit is contained in:
commit
5b09f68db9
|
@ -0,0 +1,365 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# Packaging
|
||||
pack/
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatTwo", "ChatTwo\ChatTwo.csproj", "{739F75E6-B65F-41EF-9D90-F7BC519E4875}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
namespace ChatTwo.Ui;
|
||||
|
||||
internal interface IUiComponent : IDisposable {
|
||||
void Draw();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,287 @@
|
|||
EUROPEAN UNION PUBLIC LICENCE v. 1.2
|
||||
EUPL © the European Union 2007, 2016
|
||||
|
||||
This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
|
||||
below) which is provided under the terms of this Licence. Any use of the Work,
|
||||
other than as authorised under this Licence is prohibited (to the extent such
|
||||
use is covered by a right of the copyright holder of the Work).
|
||||
|
||||
The Work is provided under the terms of this Licence when the Licensor (as
|
||||
defined below) has placed the following notice immediately following the
|
||||
copyright notice for the Work:
|
||||
|
||||
Licensed under the EUPL
|
||||
|
||||
or has expressed by any other means his willingness to license under the EUPL.
|
||||
|
||||
1. Definitions
|
||||
|
||||
In this Licence, the following terms have the following meaning:
|
||||
|
||||
- ‘The Licence’: this Licence.
|
||||
|
||||
- ‘The Original Work’: the work or software distributed or communicated by the
|
||||
Licensor under this Licence, available as Source Code and also as Executable
|
||||
Code as the case may be.
|
||||
|
||||
- ‘Derivative Works’: the works or software that could be created by the
|
||||
Licensee, based upon the Original Work or modifications thereof. This Licence
|
||||
does not define the extent of modification or dependence on the Original Work
|
||||
required in order to classify a work as a Derivative Work; this extent is
|
||||
determined by copyright law applicable in the country mentioned in Article 15.
|
||||
|
||||
- ‘The Work’: the Original Work or its Derivative Works.
|
||||
|
||||
- ‘The Source Code’: the human-readable form of the Work which is the most
|
||||
convenient for people to study and modify.
|
||||
|
||||
- ‘The Executable Code’: any code which has generally been compiled and which is
|
||||
meant to be interpreted by a computer as a program.
|
||||
|
||||
- ‘The Licensor’: the natural or legal person that distributes or communicates
|
||||
the Work under the Licence.
|
||||
|
||||
- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
|
||||
Licence, or otherwise contributes to the creation of a Derivative Work.
|
||||
|
||||
- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
|
||||
the Work under the terms of the Licence.
|
||||
|
||||
- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
|
||||
renting, distributing, communicating, transmitting, or otherwise making
|
||||
available, online or offline, copies of the Work or providing access to its
|
||||
essential functionalities at the disposal of any other natural or legal
|
||||
person.
|
||||
|
||||
2. Scope of the rights granted by the Licence
|
||||
|
||||
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||
sublicensable licence to do the following, for the duration of copyright vested
|
||||
in the Original Work:
|
||||
|
||||
- use the Work in any circumstance and for all usage,
|
||||
- reproduce the Work,
|
||||
- modify the Work, and make Derivative Works based upon the Work,
|
||||
- communicate to the public, including the right to make available or display
|
||||
the Work or copies thereof to the public and perform publicly, as the case may
|
||||
be, the Work,
|
||||
- distribute the Work or copies thereof,
|
||||
- lend and rent the Work or copies thereof,
|
||||
- sublicense rights in the Work or copies thereof.
|
||||
|
||||
Those rights can be exercised on any media, supports and formats, whether now
|
||||
known or later invented, as far as the applicable law permits so.
|
||||
|
||||
In the countries where moral rights apply, the Licensor waives his right to
|
||||
exercise his moral right to the extent allowed by law in order to make effective
|
||||
the licence of the economic rights here above listed.
|
||||
|
||||
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
|
||||
any patents held by the Licensor, to the extent necessary to make use of the
|
||||
rights granted on the Work under this Licence.
|
||||
|
||||
3. Communication of the Source Code
|
||||
|
||||
The Licensor may provide the Work either in its Source Code form, or as
|
||||
Executable Code. If the Work is provided as Executable Code, the Licensor
|
||||
provides in addition a machine-readable copy of the Source Code of the Work
|
||||
along with each copy of the Work that the Licensor distributes or indicates, in
|
||||
a notice following the copyright notice attached to the Work, a repository where
|
||||
the Source Code is easily and freely accessible for as long as the Licensor
|
||||
continues to distribute or communicate the Work.
|
||||
|
||||
4. Limitations on copyright
|
||||
|
||||
Nothing in this Licence is intended to deprive the Licensee of the benefits from
|
||||
any exception or limitation to the exclusive rights of the rights owners in the
|
||||
Work, of the exhaustion of those rights or of other applicable limitations
|
||||
thereto.
|
||||
|
||||
5. Obligations of the Licensee
|
||||
|
||||
The grant of the rights mentioned above is subject to some restrictions and
|
||||
obligations imposed on the Licensee. Those obligations are the following:
|
||||
|
||||
Attribution right: The Licensee shall keep intact all copyright, patent or
|
||||
trademarks notices and all notices that refer to the Licence and to the
|
||||
disclaimer of warranties. The Licensee must include a copy of such notices and a
|
||||
copy of the Licence with every copy of the Work he/she distributes or
|
||||
communicates. The Licensee must cause any Derivative Work to carry prominent
|
||||
notices stating that the Work has been modified and the date of modification.
|
||||
|
||||
Copyleft clause: If the Licensee distributes or communicates copies of the
|
||||
Original Works or Derivative Works, this Distribution or Communication will be
|
||||
done under the terms of this Licence or of a later version of this Licence
|
||||
unless the Original Work is expressly distributed only under this version of the
|
||||
Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
|
||||
(becoming Licensor) cannot offer or impose any additional terms or conditions on
|
||||
the Work or Derivative Work that alter or restrict the terms of the Licence.
|
||||
|
||||
Compatibility clause: If the Licensee Distributes or Communicates Derivative
|
||||
Works or copies thereof based upon both the Work and another work licensed under
|
||||
a Compatible Licence, this Distribution or Communication can be done under the
|
||||
terms of this Compatible Licence. For the sake of this clause, ‘Compatible
|
||||
Licence’ refers to the licences listed in the appendix attached to this Licence.
|
||||
Should the Licensee's obligations under the Compatible Licence conflict with
|
||||
his/her obligations under this Licence, the obligations of the Compatible
|
||||
Licence shall prevail.
|
||||
|
||||
Provision of Source Code: When distributing or communicating copies of the Work,
|
||||
the Licensee will provide a machine-readable copy of the Source Code or indicate
|
||||
a repository where this Source will be easily and freely available for as long
|
||||
as the Licensee continues to distribute or communicate the Work.
|
||||
|
||||
Legal Protection: This Licence does not grant permission to use the trade names,
|
||||
trademarks, service marks, or names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the copyright notice.
|
||||
|
||||
6. Chain of Authorship
|
||||
|
||||
The original Licensor warrants that the copyright in the Original Work granted
|
||||
hereunder is owned by him/her or licensed to him/her and that he/she has the
|
||||
power and authority to grant the Licence.
|
||||
|
||||
Each Contributor warrants that the copyright in the modifications he/she brings
|
||||
to the Work are owned by him/her or licensed to him/her and that he/she has the
|
||||
power and authority to grant the Licence.
|
||||
|
||||
Each time You accept the Licence, the original Licensor and subsequent
|
||||
Contributors grant You a licence to their contributions to the Work, under the
|
||||
terms of this Licence.
|
||||
|
||||
7. Disclaimer of Warranty
|
||||
|
||||
The Work is a work in progress, which is continuously improved by numerous
|
||||
Contributors. It is not a finished work and may therefore contain defects or
|
||||
‘bugs’ inherent to this type of development.
|
||||
|
||||
For the above reason, the Work is provided under the Licence on an ‘as is’ basis
|
||||
and without warranties of any kind concerning the Work, including without
|
||||
limitation merchantability, fitness for a particular purpose, absence of defects
|
||||
or errors, accuracy, non-infringement of intellectual property rights other than
|
||||
copyright as stated in Article 6 of this Licence.
|
||||
|
||||
This disclaimer of warranty is an essential part of the Licence and a condition
|
||||
for the grant of any rights to the Work.
|
||||
|
||||
8. Disclaimer of Liability
|
||||
|
||||
Except in the cases of wilful misconduct or damages directly caused to natural
|
||||
persons, the Licensor will in no event be liable for any direct or indirect,
|
||||
material or moral, damages of any kind, arising out of the Licence or of the use
|
||||
of the Work, including without limitation, damages for loss of goodwill, work
|
||||
stoppage, computer failure or malfunction, loss of data or any commercial
|
||||
damage, even if the Licensor has been advised of the possibility of such damage.
|
||||
However, the Licensor will be liable under statutory product liability laws as
|
||||
far such laws apply to the Work.
|
||||
|
||||
9. Additional agreements
|
||||
|
||||
While distributing the Work, You may choose to conclude an additional agreement,
|
||||
defining obligations or services consistent with this Licence. However, if
|
||||
accepting obligations, You may act only on your own behalf and on your sole
|
||||
responsibility, not on behalf of the original Licensor or any other Contributor,
|
||||
and only if You agree to indemnify, defend, and hold each Contributor harmless
|
||||
for any liability incurred by, or claims asserted against such Contributor by
|
||||
the fact You have accepted any warranty or additional liability.
|
||||
|
||||
10. Acceptance of the Licence
|
||||
|
||||
The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
|
||||
placed under the bottom of a window displaying the text of this Licence or by
|
||||
affirming consent in any other similar way, in accordance with the rules of
|
||||
applicable law. Clicking on that icon indicates your clear and irrevocable
|
||||
acceptance of this Licence and all of its terms and conditions.
|
||||
|
||||
Similarly, you irrevocably accept this Licence and all of its terms and
|
||||
conditions by exercising any rights granted to You by Article 2 of this Licence,
|
||||
such as the use of the Work, the creation by You of a Derivative Work or the
|
||||
Distribution or Communication by You of the Work or copies thereof.
|
||||
|
||||
11. Information to the public
|
||||
|
||||
In case of any Distribution or Communication of the Work by means of electronic
|
||||
communication by You (for example, by offering to download the Work from a
|
||||
remote location) the distribution channel or media (for example, a website) must
|
||||
at least provide to the public the information requested by the applicable law
|
||||
regarding the Licensor, the Licence and the way it may be accessible, concluded,
|
||||
stored and reproduced by the Licensee.
|
||||
|
||||
12. Termination of the Licence
|
||||
|
||||
The Licence and the rights granted hereunder will terminate automatically upon
|
||||
any breach by the Licensee of the terms of the Licence.
|
||||
|
||||
Such a termination will not terminate the licences of any person who has
|
||||
received the Work from the Licensee under the Licence, provided such persons
|
||||
remain in full compliance with the Licence.
|
||||
|
||||
13. Miscellaneous
|
||||
|
||||
Without prejudice of Article 9 above, the Licence represents the complete
|
||||
agreement between the Parties as to the Work.
|
||||
|
||||
If any provision of the Licence is invalid or unenforceable under applicable
|
||||
law, this will not affect the validity or enforceability of the Licence as a
|
||||
whole. Such provision will be construed or reformed so as necessary to make it
|
||||
valid and enforceable.
|
||||
|
||||
The European Commission may publish other linguistic versions or new versions of
|
||||
this Licence or updated versions of the Appendix, so far this is required and
|
||||
reasonable, without reducing the scope of the rights granted by the Licence. New
|
||||
versions of the Licence will be published with a unique version number.
|
||||
|
||||
All linguistic versions of this Licence, approved by the European Commission,
|
||||
have identical value. Parties can take advantage of the linguistic version of
|
||||
their choice.
|
||||
|
||||
14. Jurisdiction
|
||||
|
||||
Without prejudice to specific agreement between parties,
|
||||
|
||||
- any litigation resulting from the interpretation of this License, arising
|
||||
between the European Union institutions, bodies, offices or agencies, as a
|
||||
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
|
||||
of Justice of the European Union, as laid down in article 272 of the Treaty on
|
||||
the Functioning of the European Union,
|
||||
|
||||
- any litigation arising between other parties and resulting from the
|
||||
interpretation of this License, will be subject to the exclusive jurisdiction
|
||||
of the competent court where the Licensor resides or conducts its primary
|
||||
business.
|
||||
|
||||
15. Applicable Law
|
||||
|
||||
Without prejudice to specific agreement between parties,
|
||||
|
||||
- this Licence shall be governed by the law of the European Union Member State
|
||||
where the Licensor has his seat, resides or has his registered office,
|
||||
|
||||
- this licence shall be governed by Belgian law if the Licensor has no seat,
|
||||
residence or registered office inside a European Union Member State.
|
||||
|
||||
Appendix
|
||||
|
||||
‘Compatible Licences’ according to Article 5 EUPL are:
|
||||
|
||||
- GNU General Public License (GPL) v. 2, v. 3
|
||||
- GNU Affero General Public License (AGPL) v. 3
|
||||
- Open Software License (OSL) v. 2.1, v. 3.0
|
||||
- Eclipse Public License (EPL) v. 1.0
|
||||
- CeCILL v. 2.0, v. 2.1
|
||||
- Mozilla Public Licence (MPL) v. 2
|
||||
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
|
||||
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
|
||||
works other than software
|
||||
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
|
||||
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
|
||||
Reciprocity (LiLiQ-R+).
|
||||
|
||||
The European Commission may update this Appendix to later versions of the above
|
||||
licences without producing a new version of the EUPL, as long as they provide
|
||||
the rights granted in Article 2 of this Licence and protect the covered Source
|
||||
Code from exclusive appropriation.
|
||||
|
||||
All other changes or additions to this Appendix require the production of a new
|
||||
EUPL version.
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "5.0.0",
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue