chore: initial commit
This commit is contained in:
commit
f17a913405
365
.gitignore
vendored
Normal file
365
.gitignore
vendored
Normal file
@ -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
|
16
ChatTwo.sln
Executable file
16
ChatTwo.sln
Executable file
@ -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
|
59
ChatTwo/ChatTwo.csproj
Executable file
59
ChatTwo/ChatTwo.csproj
Executable file
@ -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>
|
35
ChatTwo/Chunk.cs
Executable file
35
ChatTwo/Chunk.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
91
ChatTwo/Code/ChatCode.cs
Executable file
91
ChatTwo/Code/ChatCode.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
ChatTwo/Code/ChatSource.cs
Executable file
17
ChatTwo/Code/ChatSource.cs
Executable file
@ -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,
|
||||||
|
}
|
16
ChatTwo/Code/ChatSourceExt.cs
Executable file
16
ChatTwo/Code/ChatSourceExt.cs
Executable file
@ -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;
|
||||||
|
}
|
87
ChatTwo/Code/ChatType.cs
Executable file
87
ChatTwo/Code/ChatType.cs
Executable file
@ -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,
|
||||||
|
}
|
209
ChatTwo/Code/ChatTypeExt.cs
Executable file
209
ChatTwo/Code/ChatTypeExt.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
ChatTwo/Code/InputChannel.cs
Executable file
32
ChatTwo/Code/InputChannel.cs
Executable file
@ -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,
|
||||||
|
}
|
34
ChatTwo/Code/InputChannelExt.cs
Executable file
34
ChatTwo/Code/InputChannelExt.cs
Executable file
@ -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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
59
ChatTwo/Configuration.cs
Executable file
59
ChatTwo/Configuration.cs
Executable file
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
171
ChatTwo/GameFunctions.cs
Executable file
171
ChatTwo/GameFunctions.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
17
ChatTwo/Message.cs
Executable file
17
ChatTwo/Message.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
88
ChatTwo/Plugin.cs
Executable file
88
ChatTwo/Plugin.cs
Executable file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
187
ChatTwo/PluginUi.cs
Executable file
187
ChatTwo/PluginUi.cs
Executable file
@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
168
ChatTwo/Store.cs
Executable file
168
ChatTwo/Store.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
290
ChatTwo/Ui/ChatLog.cs
Executable file
290
ChatTwo/Ui/ChatLog.cs
Executable file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
185
ChatTwo/Ui/Settings.cs
Executable file
185
ChatTwo/Ui/Settings.cs
Executable file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
ChatTwo/Ui/UiComponent.cs
Executable file
5
ChatTwo/Ui/UiComponent.cs
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
namespace ChatTwo.Ui;
|
||||||
|
|
||||||
|
internal interface IUiComponent : IDisposable {
|
||||||
|
void Draw();
|
||||||
|
}
|
76
ChatTwo/Util/ChunkUtil.cs
Executable file
76
ChatTwo/Util/ChunkUtil.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
36
ChatTwo/Util/ColourUtil.cs
Executable file
36
ChatTwo/Util/ColourUtil.cs
Executable file
@ -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);
|
||||||
|
}
|
95
ChatTwo/Util/IconUtil.cs
Executable file
95
ChatTwo/Util/IconUtil.cs
Executable file
@ -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,
|
||||||
|
};
|
||||||
|
}
|
45
ChatTwo/Util/ImGuiUtil.cs
Executable file
45
ChatTwo/Util/ImGuiUtil.cs
Executable file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
ChatTwo/fonts/NotoSans-Italic.ttf
Executable file
BIN
ChatTwo/fonts/NotoSans-Italic.ttf
Executable file
Binary file not shown.
BIN
ChatTwo/fonts/NotoSans-Regular.ttf
Executable file
BIN
ChatTwo/fonts/NotoSans-Regular.ttf
Executable file
Binary file not shown.
BIN
ChatTwo/fonts/NotoSansJP-Regular.otf
Executable file
BIN
ChatTwo/fonts/NotoSansJP-Regular.otf
Executable file
Binary file not shown.
287
LICENCE
Normal file
287
LICENCE
Normal file
@ -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.
|
7
global.json
Executable file
7
global.json
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"rollForward": "latestMajor",
|
||||||
|
"allowPrerelease": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user