CBT API 14 compatibility: Dalamud SDK 14, net10, fix ISigScanner and LocalPlayer
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled
Release Build and Publish / Release Build against Staging Dalamud and deploy to MyDalamudPlugins (release) Has been cancelled
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled
Release Build and Publish / Release Build against Staging Dalamud and deploy to MyDalamudPlugins (release) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
name: Bug
|
||||
description: For when you have found a bug
|
||||
title: "[Bug]: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What are you trying to do?
|
||||
description: What are you trying to do?
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behaviors
|
||||
attributes:
|
||||
label: What is the expected behavior?
|
||||
description: What do you think should happen?
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actually-happened
|
||||
attributes:
|
||||
label: What actually happened?
|
||||
description: Please try to be as descriptive as possible.
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: suggested-solution
|
||||
attributes:
|
||||
label: Suggested solution
|
||||
description: If you have any idea how we could solve it let me know.
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: If you have any errors in the log please put them here.
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: export
|
||||
attributes:
|
||||
label: Export
|
||||
description: If you have an export for the aura that's causing issues please provide it here.
|
||||
render: shell
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: FFXIV Update
|
||||
description: Whenever Final Fantasy has an update, XIVLauncher needs an update so please don't open issues during that window.
|
||||
options:
|
||||
- label: I have confirmed that I have the latest version of XIVLauncher and CBT.
|
||||
required: true
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Suggestion
|
||||
description: For when you want to suggest new features for CBT
|
||||
title: "[Suggestion]: "
|
||||
labels: [suggestion]
|
||||
body:
|
||||
- type: textarea
|
||||
id: suggestion
|
||||
attributes:
|
||||
label: What's your suggestion?
|
||||
description: Please try to be detailed explaining what you want.
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,116 @@
|
||||
name: Debug Build and Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build-latest:
|
||||
name: Build against Latest Dalamud
|
||||
runs-on: windows-2022
|
||||
|
||||
# Define the plugin name and Dalamud version variables for this job
|
||||
env:
|
||||
PLUGIN_NAME: CBT
|
||||
DALAMUD_VERSION_NAME: "Latest"
|
||||
DALAMUD_VERSION_URL: "https://goatcorp.github.io/dalamud-distrib/latest.zip"
|
||||
|
||||
steps:
|
||||
# Checkout the repository code
|
||||
- name: Checkout and Initialise
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install the required .NET SDK
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.x.x'
|
||||
|
||||
# Cache the nuget packages.
|
||||
- name: Cache Dependencies
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/${{ env.PLUGIN_NAME }}.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
# Create the required directory structure and download/extract Dalamud.
|
||||
- name: Download and extract Dalamud (${{ env.DALAMUD_VERSION_NAME }})
|
||||
run: |
|
||||
mkdir -p "$env:AppData\XIVLauncher\addon\Hooks\dev"
|
||||
Invoke-WebRequest -Uri "${{ env.DALAMUD_VERSION_URL }}" -OutFile "dalamud.zip"
|
||||
Expand-Archive -Path "dalamud.zip" -DestinationPath "$env:AppData\XIVLauncher\addon\Hooks\dev" -Force
|
||||
|
||||
# Restore, build, and test.
|
||||
- name: Build Debug (${{ env.DALAMUD_VERSION_NAME }})
|
||||
id: build_step
|
||||
run: |
|
||||
dotnet restore `
|
||||
&& dotnet build --no-restore --configuration Debug `
|
||||
&& dotnet test --no-build --configuration Debug
|
||||
|
||||
# Upload the build artifact. This step will only run if the build_step succeeded.
|
||||
- name: Upload Artifact (${{ env.DALAMUD_VERSION_NAME }})
|
||||
if: steps.build_step.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PLUGIN_NAME }}-debug-${{ env.DALAMUD_VERSION_NAME }}-${{ github.sha }}
|
||||
path: |
|
||||
${{ env.PLUGIN_NAME }}/bin/Debug/
|
||||
|
||||
build-staging:
|
||||
name: Build against Staging Dalamud
|
||||
runs-on: windows-2022
|
||||
|
||||
# Define the plugin name and Dalamud version variables for this job
|
||||
env:
|
||||
PLUGIN_NAME: CBT
|
||||
DALAMUD_VERSION_NAME: "Staging"
|
||||
DALAMUD_VERSION_URL: "https://goatcorp.github.io/dalamud-distrib/stg/latest.zip"
|
||||
|
||||
steps:
|
||||
# Checkout the repository code
|
||||
- name: Checkout and Initialise
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install the required .NET SDK
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.x.x'
|
||||
|
||||
# Cache the nuget packages
|
||||
- name: Cache Dependencies
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/${{ env.PLUGIN_NAME }}.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
# Create the required directory structure and download/extract Dalamud.
|
||||
- name: Download and extract Dalamud (${{ env.DALAMUD_VERSION_NAME }})
|
||||
run: |
|
||||
mkdir -p "$env:AppData\XIVLauncher\addon\Hooks\dev"
|
||||
Invoke-WebRequest -Uri "${{ env.DALAMUD_VERSION_URL }}" -OutFile "dalamud.zip"
|
||||
Expand-Archive -Path "dalamud.zip" -DestinationPath "$env:AppData\XIVLauncher\addon\Hooks\dev" -Force
|
||||
|
||||
# Restore, build, and test.
|
||||
- name: Build Debug (${{ env.DALAMUD_VERSION_NAME }})
|
||||
id: build_step
|
||||
run: |
|
||||
dotnet restore `
|
||||
&& dotnet build --no-restore --configuration Debug `
|
||||
&& dotnet test --no-build --configuration Debug
|
||||
|
||||
# Upload the build artifact.
|
||||
- name: Upload Artifact (${{ env.DALAMUD_VERSION_NAME }})
|
||||
if: steps.build_step.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PLUGIN_NAME }}-debug-${{ env.DALAMUD_VERSION_NAME }}-${{ github.sha }}
|
||||
path: |
|
||||
${{ env.PLUGIN_NAME }}/bin/Debug/
|
||||
@@ -0,0 +1,110 @@
|
||||
name: Release Build and Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-staging:
|
||||
name: Release Build against Staging Dalamud and deploy to MyDalamudPlugins
|
||||
runs-on: windows-2022
|
||||
|
||||
# Define the plugin name and Dalamud version variables for this job
|
||||
env:
|
||||
PLUGIN_NAME: CBT
|
||||
DALAMUD_VERSION_NAME: "Staging"
|
||||
DALAMUD_VERSION_URL: "https://goatcorp.github.io/dalamud-distrib/stg/latest.zip"
|
||||
|
||||
steps:
|
||||
# Checkout the repository code
|
||||
- name: Checkout and Initialise
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install the required .NET SDK
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.x.x'
|
||||
|
||||
# Cache the nuget packages
|
||||
- name: Cache Dependencies
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/${{ env.PLUGIN_NAME }}.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
# Create the required directory structure and download/extract Dalamud.
|
||||
- name: Download and extract Dalamud (${{ env.DALAMUD_VERSION_NAME }})
|
||||
run: |
|
||||
mkdir -p "$env:AppData\XIVLauncher\addon\Hooks\dev"
|
||||
Invoke-WebRequest -Uri "${{ env.DALAMUD_VERSION_URL }}" -OutFile "dalamud.zip"
|
||||
Expand-Archive -Path "dalamud.zip" -DestinationPath "$env:AppData\XIVLauncher\addon\Hooks\dev" -Force
|
||||
|
||||
# Restore, build, and test.
|
||||
- name: Build Release (${{ env.DALAMUD_VERSION_NAME }})
|
||||
id: build_step
|
||||
run: |
|
||||
dotnet restore `
|
||||
&& dotnet build --no-restore --configuration Release `
|
||||
&& dotnet test --no-build --configuration Release
|
||||
|
||||
- name: Prepare zip file
|
||||
run: |
|
||||
mkdir output
|
||||
Copy-Item "${{ env.PLUGIN_NAME }}/bin/Release/${{ env.PLUGIN_NAME }}.json" -Destination output/${{ env.PLUGIN_NAME }}.json -Force
|
||||
Compress-Archive -Path ${{ env.PLUGIN_NAME }}/bin/Release/* -DestinationPath output/latest.zip
|
||||
|
||||
- name: Upload zip to GitHub release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: output/latest.zip
|
||||
continue-on-error: true
|
||||
|
||||
- name: Clone plugin hosting repo
|
||||
run: git clone https://github.com/${{ github.repository_owner }}/MyDalamudPlugins.git
|
||||
env:
|
||||
GIT_TERMINAL_PROMPT: 0
|
||||
|
||||
- name: Copy zip to plugins folder
|
||||
run: |
|
||||
Copy-Item output/latest.zip -Destination MyDalamudPlugins/plugins/${{ env.PLUGIN_NAME }}/latest.zip -Force
|
||||
Copy-Item output/${{ env.PLUGIN_NAME }}.json -Destination MyDalamudPlugins/plugins/${{ env.PLUGIN_NAME }}/${{ env.PLUGIN_NAME }}.json -Force
|
||||
|
||||
- name: Update repo.json with version and timestamp
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pluginJsonPath = "${{ env.PLUGIN_NAME }}/bin/Release/${{ env.PLUGIN_NAME }}.json"
|
||||
$pluginJson = Get-Content $pluginJsonPath | ConvertFrom-Json
|
||||
$repoJsonPath = "MyDalamudPlugins/repo.json"
|
||||
$repoJsonRaw = Get-Content $repoJsonPath -Raw
|
||||
$repoJson = $repoJsonRaw | ConvertFrom-Json
|
||||
|
||||
if ($repoJson -isnot [System.Collections.IList]) {
|
||||
$repoJson = @($repoJson)
|
||||
}
|
||||
|
||||
foreach ($plugin in $repoJson) {
|
||||
if ($plugin.InternalName -eq $pluginJson.InternalName) {
|
||||
$plugin.DalamudApiLevel = $pluginJson.DalamudApiLevel
|
||||
$plugin.AssemblyVersion = $pluginJson.AssemblyVersion
|
||||
$plugin.LastUpdated = [int][double]::Parse((Get-Date -UFormat %s))
|
||||
}
|
||||
}
|
||||
|
||||
$repoJson | ConvertTo-Json -Depth 100 | Set-Content $repoJsonPath
|
||||
|
||||
- name: Commit and push to MyDalamudPlugins
|
||||
run: |
|
||||
cd MyDalamudPlugins
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
git add .
|
||||
git commit -m "Update ${{ env.PLUGIN_NAME }} to ${{ github.event.release.tag_name }}"
|
||||
git push https://x-access-token:${{ secrets.MYDALAMUDPLUGINS_TOKEN }}@github.com/${{ github.repository_owner }}/MyDalamudPlugins.git HEAD:main
|
||||
@@ -0,0 +1,4 @@
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
/lib/
|
||||
Generated
+13
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/modules.xml
|
||||
/.idea.CBT.iml
|
||||
/contentModel.xml
|
||||
/projectSettingsUpdater.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CBT", "CBT\CBT.csproj", "{467C443A-E383-41DC-AE5C-6EED0C62576D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9236941E-FCB4-4C19-ABA5-32D7F8CC3C2F}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Debug|x64.Build.0 = Debug|x64
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Release|Any CPU.Build.0 = Release|x64
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Release|x64.ActiveCfg = Release|x64
|
||||
{467C443A-E383-41DC-AE5C-6EED0C62576D}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E96CB457-1FB3-4953-A2A1-4EC079A48A34}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIDisposable_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6322a2c949e1408c8faf9b86b82127b8e8e910_003F64_003F0b9b5afc_003FIDisposable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace CBT.Attributes;
|
||||
|
||||
using System;
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextCategoryAttribute decorates a <see cref="FlyTextKind"/> with a Category.
|
||||
/// </summary>
|
||||
/// <param name="category">FlyTextCategory which is a collection of kinds.</param>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||||
public class FlyTextCategoryAttribute(FlyTextCategory category) : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the FlyTextCategory.
|
||||
/// </summary>
|
||||
public FlyTextCategory Category { get; } = category;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace CBT.Attributes;
|
||||
|
||||
using System;
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextFilter denotes if this text should always be filtered for a certain entity.
|
||||
/// </summary>
|
||||
public enum FlyTextFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// No-op.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// If the element should filter for self. This is probably unused.
|
||||
/// </summary>
|
||||
Self,
|
||||
|
||||
/// <summary>
|
||||
/// If the element should filter for party members.
|
||||
/// </summary>
|
||||
Party,
|
||||
|
||||
/// <summary>
|
||||
/// If the element should filter for party enemies.
|
||||
/// </summary>
|
||||
Enemy,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextFilterAttribute decorates a <see cref="FlyTextKind"/> with a Filter.
|
||||
/// </summary>
|
||||
/// <param name="filter">The kind of filter.</param>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class FlyTextFilterAttribute(FlyTextFilter[] filter) : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the FlyTextFilter.
|
||||
/// </summary>
|
||||
public FlyTextFilter[] Filter { get; } = filter;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Dalamud.NET.Sdk/14.0.1">
|
||||
<PropertyGroup>
|
||||
<Authors>cultbaus</Authors>
|
||||
<Company>-</Company>
|
||||
<Version>0.0.3.7</Version>
|
||||
<Description>This plugin manipulates flytext.</Description>
|
||||
<Copyright>-</Copyright>
|
||||
<PackageProjectUrl>https://github.com/cultbaus/CBT</PackageProjectUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(ProjectDir)Media\**" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="$(ProjectDir)Data\**" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="..\res\icon.png" Link="images\icon.png" CopyToOutputDirectory="PreserveNewest" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
{"Author":"cultbaus","Name":"CBT","InternalName":"CBT","Punchline":"A configurable ImGUI FlyText replacement.","Description":"This plugin allows the configuration and customization of in-game flytext.","RepoUrl":"http://brassnet.ddns.net:33983/KnackAtNite/CBT","IconUrl":"http://brassnet.ddns.net:33983/KnackAtNite/CBT/raw/branch/main/res/icon.png","ImageUrls":[],"ApplicableVersion":"any","DalamudApiLevel":14,"Tags":["combat","flytext","plugin"]}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,161 @@
|
||||
namespace CBT.FlyText.Animations;
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using CBT.Types;
|
||||
using FFXIVClientStructs;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextAnimationKind enumerates the kinds of animations for <see cref="FlyTextEvent"/>s.
|
||||
/// </summary>
|
||||
public enum FlyTextAnimationKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Animation no-op.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Default linear fade animation kind.
|
||||
/// </summary>
|
||||
LinearFade = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alignment for fly text animations.
|
||||
/// </summary>
|
||||
public enum FlyTextAlignment
|
||||
{
|
||||
/// <summary>
|
||||
/// Alignment no-op.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Center alignment.
|
||||
/// </summary>
|
||||
Center = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Left alignment.
|
||||
/// </summary>
|
||||
Left = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Right alignment.
|
||||
/// </summary>
|
||||
Right = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextAnimation defines the abstract class which must be implemented by Animations.
|
||||
/// </summary>
|
||||
public abstract class FlyTextAnimation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Animation duration.
|
||||
/// </summary>
|
||||
public float Duration
|
||||
=> Service.Configuration.FlyTextKinds[this.FlyTextKind].Animation.Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Animation speed.
|
||||
/// </summary>
|
||||
public float Speed
|
||||
=> Service.Configuration.FlyTextKinds[this.FlyTextKind].Animation.Speed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the animation direction should be reversed.
|
||||
/// </summary>
|
||||
public bool Reversed
|
||||
=> Service.Configuration.FlyTextKinds[this.FlyTextKind].Animation.Reversed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alignment of an element.
|
||||
/// </summary>
|
||||
public FlyTextAlignment Alignment
|
||||
=> Service.Configuration.FlyTextKinds[this.FlyTextKind].Animation.Alignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FlyAnimationKind.
|
||||
/// </summary>
|
||||
public FlyTextAnimationKind AnimationKind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FlyTextKind.
|
||||
/// </summary>
|
||||
public FlyTextKind FlyTextKind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position offset.
|
||||
/// </summary>
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Time Elapsed since created.
|
||||
/// </summary>
|
||||
public float TimeElapsed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current Alpha.
|
||||
/// </summary>
|
||||
public float Alpha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Center align an element.
|
||||
/// </summary>
|
||||
/// <param name="flyTextEvent">The event.</param>
|
||||
/// <returns>The new position after adjusting.</returns>
|
||||
public static Vector2 Center(FlyTextEvent flyTextEvent)
|
||||
{
|
||||
return new Vector2(flyTextEvent.Position.X - (flyTextEvent.Size.X / 2), flyTextEvent.Position.Y - (flyTextEvent.Size.Y / 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Left align an element.
|
||||
/// </summary>
|
||||
/// <param name="flyTextEvent">The event.</param>
|
||||
/// <returns>The new position after adjusting.</returns>
|
||||
public static Vector2 Left(FlyTextEvent flyTextEvent)
|
||||
{
|
||||
return flyTextEvent.Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of a FlyTextAnimation type.
|
||||
/// </summary>
|
||||
/// <param name="flyTextKind">FlyTextKind determines the animation type.</param>
|
||||
/// <returns>FlyTextAnimation implementation.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Exception thrown when an invalid kind is received.</exception>
|
||||
public static FlyTextAnimation Create(FlyTextKind flyTextKind)
|
||||
{
|
||||
FlyTextAnimationKind animationKind = Service.Configuration.FlyTextKinds[flyTextKind].Animation.Kind;
|
||||
return animationKind switch
|
||||
{
|
||||
FlyTextAnimationKind.None => new None(flyTextKind),
|
||||
FlyTextAnimationKind.LinearFade => new LinearFade(flyTextKind),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(flyTextKind), animationKind, null),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply calculates changes from the previous frame and updates the state of the <see cref="FlyTextEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="flyTextEvent">FlyTextEvent which is being animated.</param>
|
||||
/// <param name="timeSinceCreated">Time since the event was first drawn to the screen.</param>
|
||||
public abstract void Apply(FlyTextEvent flyTextEvent, float timeSinceCreated);
|
||||
|
||||
/// <summary>
|
||||
/// Align the element.
|
||||
/// </summary>
|
||||
/// <param name="e">Event to align.</param>
|
||||
/// <returns>The adjusted position of the element.</returns>
|
||||
public Vector2 Align(FlyTextEvent e) => this.Alignment switch
|
||||
{
|
||||
FlyTextAlignment.Center => Center(e),
|
||||
FlyTextAlignment.Left => Left(e),
|
||||
FlyTextAlignment.Right => throw new NotImplementedException(nameof(this.Alignment)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(e), this.Alignment, null),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace CBT.FlyText.Animations;
|
||||
|
||||
using System;
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// LinearFade is the default animation style for <see cref="FlyTextEvent"/>s.
|
||||
/// </summary>
|
||||
public class LinearFade : FlyTextAnimation
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinearFade"/> class.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind of the Animation parent.</param>
|
||||
public LinearFade(FlyTextKind kind)
|
||||
{
|
||||
this.FlyTextKind = kind;
|
||||
this.AnimationKind = FlyTextAnimationKind.LinearFade;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(FlyTextEvent flyTextEvent, float timeSinceCreated)
|
||||
{
|
||||
this.Offset = this.Offset with { Y = this.GetDirection(flyTextEvent, timeSinceCreated) };
|
||||
this.Alpha = Math.Max(0.0f, 1.0f - Math.Min(this.TimeElapsed / this.Duration, 1.0f));
|
||||
}
|
||||
|
||||
private float GetDirection(FlyTextEvent flyTextEvent, float timeSinceCreated)
|
||||
{
|
||||
if (flyTextEvent.Animation != null)
|
||||
{
|
||||
return flyTextEvent.Animation.Reversed
|
||||
? this.Offset.Y + (this.Speed * timeSinceCreated)
|
||||
: this.Offset.Y - (this.Speed * timeSinceCreated);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace CBT.FlyText.Animations;
|
||||
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// None is the No-op animation for <see cref="FlyTextEvent"/>s.
|
||||
/// </summary>
|
||||
public class None : FlyTextAnimation
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="None"/> class.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind for the Animation parent.</param>
|
||||
public None(FlyTextKind kind)
|
||||
{
|
||||
this.FlyTextKind = kind;
|
||||
this.AnimationKind = FlyTextAnimationKind.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply is a No-op for this type.
|
||||
/// </summary>
|
||||
/// <param name="flyTextEvent">FlyTextEvent which will not be animation.</param>
|
||||
/// <param name="timeSinceCreated">Time since the event was created.</param>
|
||||
public override void Apply(FlyTextEvent flyTextEvent, float timeSinceCreated)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
using System.Numerics;
|
||||
using CBT.FlyText.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// Defaults for configuration settings.
|
||||
/// </summary>
|
||||
public record Defaults
|
||||
{
|
||||
/// <summary>
|
||||
/// Default font.
|
||||
/// </summary>
|
||||
public const string DefaultFontName = "Expressway";
|
||||
|
||||
/// <summary>
|
||||
/// Default message format option.
|
||||
/// </summary>
|
||||
public const bool DefaultMessageFormat = true;
|
||||
|
||||
/// <summary>
|
||||
/// Default font size.
|
||||
/// </summary>
|
||||
public const float DefaultFontSize = 18f;
|
||||
|
||||
/// <summary>
|
||||
/// Default outline enabled.
|
||||
/// </summary>
|
||||
public const bool DefaultOutlineEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Default outline enabled.
|
||||
/// </summary>
|
||||
public const bool DefaultIconEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Default outline thickness.
|
||||
/// </summary>
|
||||
public const int DefaultOutlineThickness = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Default animation kind.
|
||||
/// </summary>
|
||||
public const FlyTextAnimationKind DefaultAnimationKind = FlyTextAnimationKind.LinearFade;
|
||||
|
||||
/// <summary>
|
||||
/// Default animation duration.
|
||||
/// </summary>
|
||||
public const float DefaultAnimationDuration = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Default animation speed.
|
||||
/// </summary>
|
||||
public const float DefaultAnimationSpeed = 120f;
|
||||
|
||||
/// <summary>
|
||||
/// Default icon zoom.
|
||||
/// </summary>
|
||||
public const float DefaultIconZoom = 0.7f;
|
||||
|
||||
/// <summary>
|
||||
/// Default outline reversed.
|
||||
/// </summary>
|
||||
public const bool DefaultAnimationReversed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Default outline reversed.
|
||||
/// </summary>
|
||||
public const FlyTextAlignment DefaultAnimationAlignment = FlyTextAlignment.Left;
|
||||
|
||||
/// <summary>
|
||||
/// Default font color.
|
||||
/// </summary>
|
||||
public static readonly Vector4 DefaultFontColor = Vector4.One;
|
||||
|
||||
/// <summary>
|
||||
/// Default outline color.
|
||||
/// </summary>
|
||||
public static readonly Vector4 DefaultOutlineColor = new Vector4(0, 0, 0, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Default icon size.
|
||||
/// </summary>
|
||||
public static readonly Vector2 DefaultIconSize = new Vector2(DefaultFontSize, DefaultFontSize);
|
||||
|
||||
/// <summary>
|
||||
/// Default icon offset.
|
||||
/// </summary>
|
||||
public static readonly Vector2 DefaultIconOffset = new Vector2(-10, 0);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
using CBT.FlyText.Animations;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextAnimation configuration.
|
||||
/// </summary>
|
||||
public class FlyTextAnimationConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextAnimationConfiguration"/> class.
|
||||
/// </summary>
|
||||
public FlyTextAnimationConfiguration()
|
||||
{
|
||||
this.Kind = Defaults.DefaultAnimationKind;
|
||||
this.Duration = Defaults.DefaultAnimationDuration;
|
||||
this.Speed = Defaults.DefaultAnimationSpeed;
|
||||
this.Reversed = Defaults.DefaultAnimationReversed;
|
||||
this.Alignment = Defaults.DefaultAnimationAlignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextAnimationConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="toCopy">Values to copy.</param>
|
||||
public FlyTextAnimationConfiguration(FlyTextAnimationConfiguration toCopy)
|
||||
{
|
||||
this.Kind = toCopy.Kind;
|
||||
this.Duration = toCopy.Duration;
|
||||
this.Speed = toCopy.Speed;
|
||||
this.Reversed = toCopy.Reversed;
|
||||
this.Alignment = toCopy.Alignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the animation kind.
|
||||
/// </summary>
|
||||
public FlyTextAnimationKind Kind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the duration.
|
||||
/// </summary>
|
||||
public float Duration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the speed.
|
||||
/// </summary>
|
||||
public float Speed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the animation axis should be reversed.
|
||||
/// </summary>
|
||||
public bool Reversed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment.
|
||||
/// </summary>
|
||||
public FlyTextAlignment Alignment { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
using System.Numerics;
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// FlyText configuration options.
|
||||
/// </summary>
|
||||
public class FlyTextConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextConfiguration"/> class.
|
||||
/// </summary>
|
||||
public FlyTextConfiguration()
|
||||
{
|
||||
this.Enabled = true;
|
||||
this.PreviewEnabled = false;
|
||||
this.Positionals = true;
|
||||
this.Offset = Vector2.Zero;
|
||||
this.Font = new FlyTextFontConfiguration();
|
||||
this.Animation = new FlyTextAnimationConfiguration();
|
||||
this.Icon = new FlyTextIconConfiguration();
|
||||
this.Message = new FlyTextMessageConfiguration();
|
||||
this.Filter = new FlyTextFilterConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind to read configuration for.</param>
|
||||
public FlyTextConfiguration(FlyTextKind kind)
|
||||
{
|
||||
FlyTextConfiguration config = Service.Configuration.FlyTextKinds[kind];
|
||||
|
||||
this.Enabled = config.Enabled;
|
||||
this.PreviewEnabled = config.PreviewEnabled;
|
||||
this.Positionals = config.Positionals;
|
||||
this.Offset = config.Offset;
|
||||
this.Font = new FlyTextFontConfiguration(config.Font);
|
||||
this.Animation = new FlyTextAnimationConfiguration(config.Animation);
|
||||
this.Icon = new FlyTextIconConfiguration(config.Icon);
|
||||
this.Message = new FlyTextMessageConfiguration(config.Message);
|
||||
this.Filter = new FlyTextFilterConfiguration(config.Filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="toCopy">Values to copy.</param>
|
||||
public FlyTextConfiguration(FlyTextConfiguration toCopy)
|
||||
{
|
||||
this.Enabled = toCopy.Enabled;
|
||||
this.PreviewEnabled = toCopy.PreviewEnabled;
|
||||
this.Positionals = toCopy.Positionals;
|
||||
this.Offset = toCopy.Offset;
|
||||
this.Font = new FlyTextFontConfiguration(toCopy.Font);
|
||||
this.Animation = new FlyTextAnimationConfiguration(toCopy.Animation);
|
||||
this.Icon = new FlyTextIconConfiguration(toCopy.Icon);
|
||||
this.Message = new FlyTextMessageConfiguration(toCopy.Message);
|
||||
this.Filter = new FlyTextFilterConfiguration(toCopy.Filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the kind is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the preview is enabled.
|
||||
/// </summary>
|
||||
public bool PreviewEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or positional data is enabled.
|
||||
/// </summary>
|
||||
public bool Positionals { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the persistent offset from the configuration value.
|
||||
/// </summary>
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the font configuration.
|
||||
/// </summary>
|
||||
public FlyTextFontConfiguration Font { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the animation configuration.
|
||||
/// </summary>
|
||||
public FlyTextAnimationConfiguration Animation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon configuration.
|
||||
/// </summary>
|
||||
public FlyTextIconConfiguration Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message configuration.
|
||||
/// </summary>
|
||||
public FlyTextMessageConfiguration Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filter configuration.
|
||||
/// </summary>
|
||||
public FlyTextFilterConfiguration Filter { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextFilter configuration.
|
||||
/// </summary>
|
||||
public class FlyTextFilterConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextFilterConfiguration"/> class.
|
||||
/// </summary>
|
||||
public FlyTextFilterConfiguration()
|
||||
{
|
||||
this.Self = true;
|
||||
this.Enemy = true;
|
||||
this.Party = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextFilterConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="toCopy">Configuration to copy from.</param>
|
||||
public FlyTextFilterConfiguration(FlyTextFilterConfiguration toCopy)
|
||||
{
|
||||
this.Self = toCopy.Self;
|
||||
this.Enemy = toCopy.Enemy;
|
||||
this.Party = toCopy.Party;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this is enabled for self.
|
||||
/// </summary>
|
||||
public bool Self { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this is enabled for enemies.
|
||||
/// </summary>
|
||||
public bool Enemy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this is enabled for part members.
|
||||
/// </summary>
|
||||
public bool Party { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
using System.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextFont configuration options.
|
||||
/// </summary>
|
||||
public class FlyTextFontConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextFontConfiguration"/> class.
|
||||
/// </summary>
|
||||
public FlyTextFontConfiguration()
|
||||
{
|
||||
this.Outline = new FlyTextOutlineConfiguration();
|
||||
this.Size = Defaults.DefaultFontSize;
|
||||
this.Name = Defaults.DefaultFontName;
|
||||
this.Color = Defaults.DefaultFontColor;
|
||||
this.ColorSuccess = Defaults.DefaultFontColor;
|
||||
this.ColorFailed = Defaults.DefaultFontColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextFontConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="toCopy">Copies values from this.</param>
|
||||
public FlyTextFontConfiguration(FlyTextFontConfiguration toCopy)
|
||||
{
|
||||
this.Outline = toCopy.Outline;
|
||||
this.Size = toCopy.Size;
|
||||
this.Name = toCopy.Name;
|
||||
this.Color = toCopy.Color;
|
||||
this.ColorSuccess = toCopy.ColorSuccess;
|
||||
this.ColorFailed = toCopy.ColorFailed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the outline configuration.
|
||||
/// </summary>
|
||||
public FlyTextOutlineConfiguration Outline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the font.
|
||||
/// </summary>
|
||||
public float Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the font.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the font.
|
||||
/// </summary>
|
||||
public Vector4 Color { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the font for positional success.
|
||||
/// </summary>
|
||||
public Vector4 ColorSuccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the font for positional failures.
|
||||
/// </summary>
|
||||
public Vector4 ColorFailed { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
using System.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// FlyText Icon configuration.
|
||||
/// </summary>
|
||||
public class FlyTextIconConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextIconConfiguration"/> class.
|
||||
/// </summary>
|
||||
public FlyTextIconConfiguration()
|
||||
{
|
||||
this.Size = Defaults.DefaultIconSize;
|
||||
this.Zoom = Defaults.DefaultIconZoom;
|
||||
this.Offset = Defaults.DefaultIconOffset;
|
||||
this.Enabled = Defaults.DefaultIconEnabled;
|
||||
this.Outline = new FlyTextOutlineConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextIconConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="iconSize">Size of the icon in pixels.</param>
|
||||
/// <param name="iconZoom">Zoom ratio of the icon.</param>
|
||||
/// <param name="iconEnabled">Icon is enabled.</param>
|
||||
/// <param name="iconOffset">Icon offset from the text.</param>
|
||||
/// <param name="outlineConfig">Outline configuration.</param>
|
||||
public FlyTextIconConfiguration(Vector2 iconSize, float iconZoom, Vector2 iconOffset, bool iconEnabled, FlyTextOutlineConfiguration outlineConfig)
|
||||
{
|
||||
this.Outline = outlineConfig;
|
||||
this.Size = iconSize;
|
||||
this.Offset = iconOffset;
|
||||
this.Zoom = iconZoom;
|
||||
this.Enabled = iconEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextIconConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="toCopy">Configuration to copy values from.</param>
|
||||
public FlyTextIconConfiguration(FlyTextIconConfiguration toCopy)
|
||||
{
|
||||
this.Outline = new FlyTextOutlineConfiguration(toCopy.Outline);
|
||||
this.Size = toCopy.Size;
|
||||
this.Offset = toCopy.Offset;
|
||||
this.Zoom = toCopy.Zoom;
|
||||
this.Enabled = toCopy.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the outline configuration.
|
||||
/// </summary>
|
||||
public FlyTextOutlineConfiguration Outline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the icon.
|
||||
/// </summary>
|
||||
public Vector2 Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offset of the icon.
|
||||
/// </summary>
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the zoom ratio of the icon.
|
||||
/// </summary>
|
||||
public float Zoom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether an icon is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextMessageConfiguration configures the fly text message.
|
||||
/// </summary>
|
||||
public class FlyTextMessageConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextMessageConfiguration"/> class.
|
||||
/// </summary>
|
||||
public FlyTextMessageConfiguration()
|
||||
{
|
||||
this.Prefix = string.Empty;
|
||||
this.Suffix = string.Empty;
|
||||
this.Format = Defaults.DefaultMessageFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextMessageConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="prefix">What to prefix the message with.</param>
|
||||
/// <param name="suffix">What to postfix the message with.</param>
|
||||
/// <param name="format">Whether or not to format the string.</param>
|
||||
public FlyTextMessageConfiguration(string prefix, string suffix, bool format)
|
||||
{
|
||||
this.Prefix = prefix;
|
||||
this.Suffix = suffix;
|
||||
this.Format = format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextMessageConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="toCopy">Configuration to copy from.</param>
|
||||
public FlyTextMessageConfiguration(FlyTextMessageConfiguration toCopy)
|
||||
{
|
||||
this.Prefix = toCopy.Prefix;
|
||||
this.Suffix = toCopy.Suffix;
|
||||
this.Format = toCopy.Format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message prefix.
|
||||
/// </summary>
|
||||
public string Prefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message suffix.
|
||||
/// </summary>
|
||||
public string Suffix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the string should be formatted.
|
||||
/// </summary>
|
||||
public bool Format { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace CBT.FlyText.Configuration;
|
||||
|
||||
using System.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextOutline configuration options.
|
||||
/// </summary>
|
||||
public class FlyTextOutlineConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextOutlineConfiguration"/> class.
|
||||
/// </summary>
|
||||
public FlyTextOutlineConfiguration()
|
||||
{
|
||||
this.Enabled = Defaults.DefaultOutlineEnabled;
|
||||
this.Size = Defaults.DefaultOutlineThickness;
|
||||
this.Color = Defaults.DefaultOutlineColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextOutlineConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="enabled">If the outline is enabled.</param>
|
||||
/// <param name="size">Size of the outline in pixels.</param>
|
||||
/// <param name="color">Color of the outline.</param>
|
||||
public FlyTextOutlineConfiguration(bool enabled, int size, Vector4 color)
|
||||
{
|
||||
this.Enabled = enabled;
|
||||
this.Size = size;
|
||||
this.Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextOutlineConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="toCopy">Values to copy.</param>
|
||||
public FlyTextOutlineConfiguration(FlyTextOutlineConfiguration toCopy)
|
||||
{
|
||||
this.Enabled = toCopy.Enabled;
|
||||
this.Size = toCopy.Size;
|
||||
this.Color = toCopy.Color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the outline is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Size of the outline.
|
||||
/// </summary>
|
||||
public int Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the outline.
|
||||
/// </summary>
|
||||
public Vector4 Color { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace CBT.FlyText;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Object pool for to re-use memory for FlyText Events and hopefully not thrash the gc.
|
||||
/// </summary>
|
||||
public class FlyTextPool : IDisposable
|
||||
{
|
||||
private readonly ConcurrentBag<FlyTextEvent> pool = [];
|
||||
|
||||
/// <summary>
|
||||
/// Get a fly text event instance.
|
||||
/// </summary>
|
||||
/// <returns>A flytext event.</returns>
|
||||
public FlyTextEvent Get()
|
||||
=> this.pool.TryTake(out var e) ? e : new FlyTextEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Put a fly text event back into the pool.
|
||||
/// </summary>
|
||||
/// <param name="e">fly text event.</param>
|
||||
public void Put(FlyTextEvent e)
|
||||
{
|
||||
e.Reset();
|
||||
this.pool.Add(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.pool.Clear();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
namespace CBT.FlyText;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CBT.Attributes;
|
||||
using CBT.FlyText.Configuration;
|
||||
using CBT.Types;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using static FFXIVClientStructs.FFXIV.Client.Game.Character.ActionEffectHandler;
|
||||
using DalamudFlyText = Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextReceiver receives FlyText from the game client.
|
||||
/// </summary>
|
||||
public unsafe partial class FlyTextReceiver : IDisposable
|
||||
{
|
||||
private readonly Hook<AddScreenLogWithKindDelegate> addScreenLogWithKindHook;
|
||||
private readonly Hook<AddScreenLogDelegate> addScreenLogHook;
|
||||
private readonly Hook<ReceiveActionEffectDelegate> receiveActionEffectHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextReceiver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="gameInteropProvider">Dalamud game interop provider.</param>
|
||||
public FlyTextReceiver(IGameInteropProvider gameInteropProvider)
|
||||
{
|
||||
Service.FlyTextGui.FlyTextCreated += this.FlyTextCreated;
|
||||
|
||||
this.addScreenLogWithKindHook = gameInteropProvider.HookFromAddress<AddScreenLogWithKindDelegate>(Service.Address.AddScreenLogWithKind, this.AddScreenLogWithKindDetour);
|
||||
this.addScreenLogWithKindHook.Enable();
|
||||
|
||||
this.addScreenLogHook = gameInteropProvider.HookFromAddress<AddScreenLogDelegate>(Service.Address.AddScreenLog, this.AddScreenLogDetour);
|
||||
// this.addScreenLogHook.Enable();
|
||||
|
||||
this.receiveActionEffectHook = gameInteropProvider.HookFromAddress<ReceiveActionEffectDelegate>(Service.Address.ReceiveActionEffect, this.ReceiveActionEffectDetour);
|
||||
// this.receiveActionEffectHook.Enable();
|
||||
}
|
||||
|
||||
private delegate void AddScreenLogWithKindDelegate(
|
||||
Character* target,
|
||||
Character* source,
|
||||
FlyTextKind kind,
|
||||
int option,
|
||||
int actionKind,
|
||||
int actionID,
|
||||
int val1,
|
||||
int val2,
|
||||
int val3,
|
||||
int val4);
|
||||
|
||||
private delegate void AddScreenLogDelegate(long screenLogManager, FlyTextCreation* flyTextCreation);
|
||||
|
||||
private delegate void ReceiveActionEffectDelegate(
|
||||
uint casterEntityId,
|
||||
Character* casterPtr,
|
||||
Vector3* targetPos,
|
||||
Header* header,
|
||||
TargetEffects* effects,
|
||||
GameObjectId* targetEntityIds);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.addScreenLogWithKindHook.Disable();
|
||||
this.addScreenLogWithKindHook.Dispose();
|
||||
|
||||
// this.addScreenLogHook.Disable();
|
||||
this.addScreenLogHook.Dispose();
|
||||
|
||||
// this.receiveActionEffectHook.Disable();
|
||||
this.receiveActionEffectHook.Dispose();
|
||||
|
||||
Service.FlyTextGui.FlyTextCreated -= this.FlyTextCreated;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void AddScreenLogWithKindDetour(
|
||||
Character* target,
|
||||
Character* source,
|
||||
FlyTextKind kind,
|
||||
int option,
|
||||
int actionKind,
|
||||
int actionID,
|
||||
int val1,
|
||||
int val2,
|
||||
int val3,
|
||||
int val4)
|
||||
{
|
||||
try
|
||||
{
|
||||
var kindConfig = PluginManager.GetConfigForKind(kind);
|
||||
var effects = GetEffects(target->GetActionEffectHandler());
|
||||
var sourceObjectID = GetGameObjectId(target->GetActionEffectHandler());
|
||||
|
||||
if (ShouldManageEvent(kind, source, target, sourceObjectID.ObjectId, kindConfig))
|
||||
{
|
||||
var flyTextEvent = Service.Pool.Get();
|
||||
flyTextEvent.Hydrate(kind, effects, sourceObjectID.ObjectId, target, source, option, actionKind, actionID, val1, val2, val3, val4);
|
||||
|
||||
Service.Manager.Add(flyTextEvent);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Service.PluginLog.Error($"CBT Wizard used Testicular Torsion: {ex.Message}");
|
||||
}
|
||||
|
||||
this.addScreenLogWithKindHook.Original(target, source, kind, option, actionKind, actionID, val1, val2, val3, val4);
|
||||
}
|
||||
|
||||
private void AddScreenLogDetour(long screenLogManager, FlyTextCreation* flyTextCreation)
|
||||
{
|
||||
this.addScreenLogHook.Original(screenLogManager, flyTextCreation);
|
||||
}
|
||||
|
||||
private void ReceiveActionEffectDetour(
|
||||
uint casterEntityId,
|
||||
Character* casterPtr,
|
||||
Vector3* targetPos,
|
||||
Header* header,
|
||||
TargetEffects* effects,
|
||||
GameObjectId* targetEntityIds)
|
||||
{
|
||||
this.receiveActionEffectHook.Original(casterEntityId, casterPtr, targetPos, header, effects, targetEntityIds);
|
||||
}
|
||||
|
||||
private void FlyTextCreated(
|
||||
ref DalamudFlyText.FlyTextKind kind,
|
||||
ref int val1,
|
||||
ref int val2,
|
||||
ref SeString text1,
|
||||
ref SeString text2,
|
||||
ref uint color,
|
||||
ref uint icon,
|
||||
ref uint damageTypeIcon,
|
||||
ref float yOffset,
|
||||
ref bool handled)
|
||||
{
|
||||
if (Service.Configuration.Options.TryGetValue(GlobalOption.NativeUnhandled.ToString(), out var allowNativeEvents))
|
||||
{
|
||||
var kindConfig = PluginManager.GetConfigForKind((FlyTextKind)kind);
|
||||
|
||||
if (kindConfig == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (allowNativeEvents)
|
||||
{
|
||||
handled = kindConfig.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
Service.PluginLog.Info($"FlyTextKind: {kind} was not handled by CBT.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bad guy stuff.
|
||||
/// </summary>
|
||||
public unsafe partial class FlyTextReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// There's a pointer to the action effect handler on the target that allows me to get at the action effects.
|
||||
/// I can get the damage type from this, otherwise I need to share state between two delegates, and pointer arithmetic
|
||||
/// seemed like the lesser of two evils.
|
||||
/// </summary>
|
||||
/// <param name="handler">Action Effect Handler associated with the target.</param>
|
||||
/// <param name="effectEntryIndex">EffectEntry index. Maybe this changes someday.</param>
|
||||
/// <returns>A copy of of all effects that an action had.</returns>
|
||||
private static Effect[] GetEffects(ActionEffectHandler* handler, int effectEntryIndex = 0)
|
||||
{
|
||||
var effectEntryPtr = (byte*)handler + (effectEntryIndex * 0x78);
|
||||
var targetEffectsPtr = (TargetEffects*)(effectEntryPtr + 0x38);
|
||||
var effectsSpan = targetEffectsPtr->Effects;
|
||||
|
||||
return effectsSpan.ToArray();
|
||||
}
|
||||
|
||||
private static GameObjectId GetGameObjectId(ActionEffectHandler* handler, int effectEntryIndex = 0)
|
||||
{
|
||||
var effectEntryPtr = (byte*)handler + (effectEntryIndex * 0x78);
|
||||
var gameObjectID = (GameObjectId*)(effectEntryPtr + 0x18);
|
||||
|
||||
return *gameObjectID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if an event should be managed by the plugin or ignored.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind of the event. Unused.</param>
|
||||
/// <param name="source">Source of the event.</param>
|
||||
/// <param name="target">Target of the event.</param>
|
||||
/// <param name="sourceObjectID">Caster of the event.</param>
|
||||
/// <param name="kindConfig">Configuration for the current kind.</param>
|
||||
/// <returns>A bool indicating whether the plugin should manage the flytext event.</returns>
|
||||
private static bool ShouldManageEvent(FlyTextKind kind, Character* source, Character* target, GameObjectId sourceObjectID, FlyTextConfiguration? kindConfig)
|
||||
{
|
||||
if (kindConfig == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!kindConfig.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == null || source == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PluginManager.GetDistance(target) > 60)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var isOurAbility = PluginManager.IsPlayerCharacter(source);
|
||||
var isAgainstUs = PluginManager.IsPlayerCharacter(target);
|
||||
|
||||
var isPartyAbility = PluginManager.IsPartyMember(source);
|
||||
var isAgainstParty = PluginManager.IsPartyMember(target);
|
||||
|
||||
var isEnemyAbility = PluginManager.IsEnemy(source);
|
||||
var isAgainstEnemy = PluginManager.IsEnemy(target);
|
||||
|
||||
var specialCaseForHpRegen =
|
||||
kind == FlyTextKind.Healing
|
||||
&& PluginManager.IsPartyMember(source)
|
||||
&& PluginManager.LocalPlayer?.GameObjectId == sourceObjectID.ObjectId;
|
||||
|
||||
var conditionTable = new (bool Condition, FlyTextFilter Filter)[]
|
||||
{
|
||||
(isOurAbility && isAgainstEnemy && kindConfig.Filter.Enemy, FlyTextFilter.Enemy),
|
||||
(isOurAbility && isAgainstUs && kindConfig.Filter.Self, FlyTextFilter.Self),
|
||||
(isOurAbility && isAgainstParty && kindConfig.Filter.Party, FlyTextFilter.Party),
|
||||
(isPartyAbility && isAgainstUs && kindConfig.Filter.Self, FlyTextFilter.Self),
|
||||
(isEnemyAbility && isAgainstUs && kindConfig.Filter.Self, FlyTextFilter.Self),
|
||||
(specialCaseForHpRegen && kindConfig.Filter.Party, FlyTextFilter.Party),
|
||||
};
|
||||
|
||||
return conditionTable.Any(c => c.Condition && kind.ShouldAllow(c.Filter));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Suppress warnings :)", Scope = "module")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Not my job :)", Scope = "module")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "This sucks", Scope = "module")]
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// Font is a Dalamud IFontHandle with a specific name and size.
|
||||
/// </summary>
|
||||
public class Font : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Font"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handle">Dalamud font handle.</param>
|
||||
/// <param name="name">Font name.</param>
|
||||
/// <param name="size">Font size in pixels.</param>
|
||||
public Font(IFontHandle handle, string name, float size)
|
||||
{
|
||||
this.Handle = handle;
|
||||
this.Name = name;
|
||||
this.Size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Dalamud IFontHandle.
|
||||
/// </summary>
|
||||
public IFontHandle Handle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the font.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the font in pixels.
|
||||
/// </summary>
|
||||
public float Size { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
=> this.Handle?.Pop();
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the Dalamud IFontHandle into the ImGUI stack.
|
||||
/// </summary>
|
||||
/// <returns>A Dalamud IFontHandle scope.</returns>
|
||||
public Font Push()
|
||||
{
|
||||
this.Handle?.Push();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// FontManager loads fonts from the Media directory and exposes a Push API to scope font handles.
|
||||
/// </summary>
|
||||
public class FontManager : IDisposable
|
||||
{
|
||||
private readonly string mediaPath;
|
||||
private readonly List<Font> fonts = new List<Font>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FontManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="mediaPath">Path to the Media directory where fonts are located.</param>
|
||||
public FontManager(string mediaPath)
|
||||
{
|
||||
this.mediaPath = mediaPath;
|
||||
|
||||
this.LoadAllFonts();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.fonts?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a Dalamud FontHandle into the current scope.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the font to push.</param>
|
||||
/// <param name="size">Size of the font to push.</param>
|
||||
/// <returns>A <see cref="Font"/> instance which will Pop once it goes out of scope.</returns>
|
||||
public Font? Push(string name, float size)
|
||||
{
|
||||
return this.fonts.FirstOrDefault(f => f.Name == name && f.Size == size)?.Push();
|
||||
}
|
||||
|
||||
private void LoadAllFonts()
|
||||
{
|
||||
Directory.GetFiles(this.mediaPath, "*.ttf")
|
||||
.Select(file => Path.GetFileNameWithoutExtension(file))
|
||||
.ToList()
|
||||
.ForEach(file =>
|
||||
{
|
||||
Enumerable.Range(14, 32 - 14 + 1)
|
||||
.Where(i => i % 2 == 0)
|
||||
.ToList()
|
||||
.ForEach(size =>
|
||||
{
|
||||
this.LoadFont(file, size);
|
||||
});
|
||||
});
|
||||
|
||||
PluginConfiguration.Fonts = this.fonts
|
||||
.GroupBy(font => font.Name)
|
||||
.ToDictionary(
|
||||
font => font.Key,
|
||||
font => font.Select(f => f.Size).ToList());
|
||||
}
|
||||
|
||||
private void LoadFont(string fontName, float size)
|
||||
{
|
||||
try
|
||||
{
|
||||
IFontHandle fontHandle = Service.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||
{
|
||||
e.OnPreBuild(tk => tk.AddFontFromFile(Path.Combine(this.mediaPath, $"{fontName}.ttf"), new SafeFontConfig { SizePx = size }));
|
||||
});
|
||||
|
||||
if (fontHandle != null)
|
||||
{
|
||||
this.fonts.Add(new Font(fontHandle, fontName, size));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Service.PluginLog.Error($"Error loading font from media path: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
namespace CBT.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
public record Manifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or initializes the Author of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Author { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the Name of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the Punchline of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Punchline { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the Description for the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the RepoURL for the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? RepoURL { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the IconURL for the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? IconURL { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the Image URLs for the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public List<string>? ImageURLs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the Dalamud API level for the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public int? DalamuAPILevel { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the Tags for the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public List<string>? Tags { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManifestManager"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ManifestManager loads the Plugin manifest from the assembly directory.
|
||||
/// </remarks>
|
||||
/// <param name="manifestPath">Path to the Assembly directory where manifest is located.</param>
|
||||
public class ManifestManager(string manifestPath) : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the PluginManifest.
|
||||
/// </summary>
|
||||
public Manifest? Manifest { get; private set; } = JsonConvert.DeserializeObject<Manifest>(File.ReadAllText(manifestPath)) ?? new Manifest();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// State a positional action can be in.
|
||||
/// </summary>
|
||||
public enum PositionalState
|
||||
{
|
||||
/// <summary>
|
||||
/// Not a positional action.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Positional succeeded.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// Positional failed.
|
||||
/// </summary>
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dragoon ability actions.
|
||||
/// </summary>
|
||||
public enum Dragoon : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Dragoon positional action.
|
||||
/// </summary>
|
||||
ChaosThrust = 88,
|
||||
|
||||
/// <summary>
|
||||
/// Dragoon positional action.
|
||||
/// </summary>
|
||||
FangAndClaw = 3554,
|
||||
|
||||
/// <summary>
|
||||
/// Dragoon positional action.
|
||||
/// </summary>
|
||||
WheelingThrust = 3556,
|
||||
|
||||
/// <summary>
|
||||
/// Dragoon positional action.
|
||||
/// </summary>
|
||||
ChaoticSpring = 25772,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monk ability actions.
|
||||
/// </summary>
|
||||
public enum Monk : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Monk positional action.
|
||||
/// </summary>
|
||||
SnapPunch = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Monk positional action.
|
||||
/// </summary>
|
||||
Demolish = 66,
|
||||
|
||||
/// <summary>
|
||||
/// Monk positional action.
|
||||
/// </summary>
|
||||
PouncingCoeurl = 36947,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ninja ability actions.
|
||||
/// </summary>
|
||||
public enum Ninja
|
||||
{
|
||||
/// <summary>
|
||||
/// Ninja positional action.
|
||||
/// </summary>
|
||||
AeolianEdge = 2255,
|
||||
|
||||
/// <summary>
|
||||
/// Ninja positional action.
|
||||
/// </summary>
|
||||
TrickAttack = 2258,
|
||||
|
||||
/// <summary>
|
||||
/// Ninja positional action.
|
||||
/// </summary>
|
||||
ArmorCrush = 3563,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reaper ability actions.
|
||||
/// </summary>
|
||||
public enum Reaper
|
||||
{
|
||||
/// <summary>
|
||||
/// Reaper positional action.
|
||||
/// </summary>
|
||||
Gibbet = 24382,
|
||||
|
||||
/// <summary>
|
||||
/// Reaper positional action.
|
||||
/// </summary>
|
||||
Gallows = 24383,
|
||||
|
||||
/// <summary>
|
||||
/// Reaper positional action.
|
||||
/// </summary>
|
||||
ExecutionersGibbet = 36970,
|
||||
|
||||
/// <summary>
|
||||
/// Reaper positional action.
|
||||
/// </summary>
|
||||
ExecutionersGallows = 36971,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samurai ability actions.
|
||||
/// </summary>
|
||||
public enum Samurai
|
||||
{
|
||||
/// <summary>
|
||||
/// Samurai positional action.
|
||||
/// </summary>
|
||||
Gekko = 7481,
|
||||
|
||||
/// <summary>
|
||||
/// Samurai positional action.
|
||||
/// </summary>
|
||||
Kasha = 7482,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Viper ability actions.
|
||||
/// </summary>
|
||||
public enum Viper : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Viper positional action.
|
||||
/// </summary>
|
||||
FlankstingStrike = 34610,
|
||||
|
||||
/// <summary>
|
||||
/// Viper positional action.
|
||||
/// </summary>
|
||||
FlanksbaneFang = 34611,
|
||||
|
||||
/// <summary>
|
||||
/// Viper positional action.
|
||||
/// </summary>
|
||||
HindstringStrike = 34612,
|
||||
|
||||
/// <summary>
|
||||
/// Viper positional action.
|
||||
/// </summary>
|
||||
HindsbaneFang = 34613,
|
||||
|
||||
/// <summary>
|
||||
/// Viper positional action.
|
||||
/// </summary>
|
||||
HuntersCoil = 34621,
|
||||
|
||||
/// <summary>
|
||||
/// Viper positional action.
|
||||
/// </summary>
|
||||
SwiftskinsCoil = 34622,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Positional Manager for positional data.
|
||||
/// </summary>
|
||||
public class PositionalManager
|
||||
{
|
||||
private static readonly Dictionary<uint, HashSet<int>> PositionalData;
|
||||
|
||||
static PositionalManager() => PositionalData = new()
|
||||
{
|
||||
{ (uint)Dragoon.ChaosThrust, [28, 61] },
|
||||
{ (uint)Dragoon.FangAndClaw, [22, 28, 58, 66] },
|
||||
{ (uint)Dragoon.WheelingThrust, [22, 28, 58, 66] },
|
||||
{ (uint)Dragoon.ChaoticSpring, [22, 28, 58, 66] },
|
||||
{ (uint)Monk.SnapPunch, [12, 13, 14, 18, 20] },
|
||||
{ (uint)Monk.Demolish, [14, 15, 17, 18] },
|
||||
{ (uint)Monk.PouncingCoeurl, [11, 16] },
|
||||
{ (uint)Ninja.AeolianEdge, [16, 20, 23, 30, 44, 50, 54, 63, 70] },
|
||||
{ (uint)Ninja.TrickAttack, [25] },
|
||||
{ (uint)Ninja.ArmorCrush, [21, 30, 54, 65] },
|
||||
{ (uint)Reaper.Gibbet, [9, 10, 11, 13] },
|
||||
{ (uint)Reaper.Gallows, [9, 10, 11, 13] },
|
||||
{ (uint)Reaper.ExecutionersGibbet, [7] },
|
||||
{ (uint)Reaper.ExecutionersGallows, [7] },
|
||||
{ (uint)Samurai.Gekko, [23, 31, 33, 61, 70, 72] },
|
||||
{ (uint)Samurai.Kasha, [23, 31, 33, 61, 70, 72] },
|
||||
{ (uint)Viper.FlankstingStrike, [48, 50, 54, 60, 63, 70] },
|
||||
{ (uint)Viper.FlanksbaneFang, [48, 50, 54, 60, 63, 70] },
|
||||
{ (uint)Viper.HindstringStrike, [48, 50, 54, 60, 63, 70] },
|
||||
{ (uint)Viper.HindsbaneFang, [48, 50, 54, 60, 63, 70] },
|
||||
{ (uint)Viper.HuntersCoil, [8] },
|
||||
{ (uint)Viper.SwiftskinsCoil, [8] },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Compare known positional data with received effect data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The positional data isn't exhaustive and also considers valid positionals which fail combo actions to be successful.
|
||||
/// </remarks>
|
||||
/// <param name="actionID">The ID of the action to check.</param>
|
||||
/// <param name="modifier">The `Param2` value on the Effects span for a damage action.</param>
|
||||
/// <returns>Positional state indicating the success state.</returns>
|
||||
public static PositionalState PositionalSucceeded(int actionID, byte modifier)
|
||||
=> PositionalData.ContainsKey((uint)actionID)
|
||||
? PositionalData.TryGetValue((uint)actionID, out var successModifiers) && successModifiers.Contains(modifier) ? PositionalState.Success : PositionalState.Failed
|
||||
: PositionalState.None;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuadTree"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We need a quad tree because the naive implementation wasn't fucking working.
|
||||
/// </remarks>
|
||||
/// <param name="level">Level.</param>
|
||||
/// <param name="bounds">Bounds.</param>
|
||||
public class QuadTree(int level, Rectangle bounds)
|
||||
{
|
||||
private const int MaxEvents = 10;
|
||||
private const int MaxLevels = 5;
|
||||
|
||||
private readonly int level = level;
|
||||
private readonly List<FlyTextEvent> events = [];
|
||||
private readonly Rectangle bounds = bounds;
|
||||
private readonly QuadTree[] nodes = new QuadTree[4];
|
||||
|
||||
/// <summary>
|
||||
/// Clear the tree.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
this.events.Clear();
|
||||
for (int i = 0; i < this.nodes.Length; i++)
|
||||
{
|
||||
if (this.nodes[i] != null)
|
||||
{
|
||||
this.nodes[i].Clear();
|
||||
this.nodes[i] = null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a new event into the tree.
|
||||
/// </summary>
|
||||
/// <param name="e">Event.</param>
|
||||
public void Insert(FlyTextEvent e)
|
||||
{
|
||||
if (this.nodes[0] != null)
|
||||
{
|
||||
var index = this.GetIndex(e);
|
||||
if (index != -1)
|
||||
{
|
||||
this.nodes[index].Insert(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.events.Add(e);
|
||||
|
||||
if (this.events.Count > MaxEvents && this.level < MaxLevels)
|
||||
{
|
||||
if (this.nodes[0] == null)
|
||||
{
|
||||
this.Split();
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
while (i < this.events.Count)
|
||||
{
|
||||
int index = this.GetIndex(this.events[i]);
|
||||
if (index != -1)
|
||||
{
|
||||
this.nodes[index].Insert(this.events[i]);
|
||||
this.events.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retreieve a copy of potential collisions.
|
||||
/// </summary>
|
||||
/// <param name="potentialCollisions">Events.</param>
|
||||
/// <param name="e">Event.</param>
|
||||
/// <returns>Potential collisions for this event.</returns>
|
||||
public List<FlyTextEvent> Retrieve(List<FlyTextEvent> potentialCollisions, FlyTextEvent e)
|
||||
{
|
||||
var index = this.GetIndex(e);
|
||||
if (index != -1 && this.nodes[0] != null)
|
||||
{
|
||||
this.nodes[index].Retrieve(potentialCollisions, e);
|
||||
}
|
||||
|
||||
potentialCollisions.AddRange(this.events);
|
||||
|
||||
return potentialCollisions;
|
||||
}
|
||||
|
||||
private void Split()
|
||||
{
|
||||
var subWidth = this.bounds.Width / 2;
|
||||
var subHeight = this.bounds.Height / 2;
|
||||
var x = this.bounds.X;
|
||||
var y = this.bounds.Y;
|
||||
|
||||
this.nodes[0] = new QuadTree(this.level + 1, new Rectangle(x + subWidth, y, subWidth, subHeight));
|
||||
this.nodes[1] = new QuadTree(this.level + 1, new Rectangle(x, y, subWidth, subHeight));
|
||||
this.nodes[2] = new QuadTree(this.level + 1, new Rectangle(x, y + subHeight, subWidth, subHeight));
|
||||
this.nodes[3] = new QuadTree(this.level + 1, new Rectangle(x + subWidth, y + subHeight, subWidth, subHeight));
|
||||
}
|
||||
|
||||
private int GetIndex(FlyTextEvent e)
|
||||
{
|
||||
var index = -1;
|
||||
var rect = new Rectangle(e.Position.X, e.Position.Y, e.Size.X, e.Size.Y);
|
||||
var verticalMidpoint = this.bounds.X + (this.bounds.Width / 2);
|
||||
var horizontalMidpoint = this.bounds.Y + (this.bounds.Height / 2);
|
||||
|
||||
var topQuadrant = rect.Y < horizontalMidpoint && rect.Y + rect.Height < horizontalMidpoint;
|
||||
var bottomQuadrant = rect.Y > horizontalMidpoint;
|
||||
|
||||
if (rect.X < verticalMidpoint && rect.X + rect.Width < verticalMidpoint)
|
||||
{
|
||||
if (topQuadrant)
|
||||
{
|
||||
index = 1;
|
||||
}
|
||||
else if (bottomQuadrant)
|
||||
{
|
||||
index = 2;
|
||||
}
|
||||
}
|
||||
else if (rect.X > verticalMidpoint)
|
||||
{
|
||||
if (topQuadrant)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (bottomQuadrant)
|
||||
{
|
||||
index = 3;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Quad Tree manager. This should be smarter than it is, but for now, it just is.
|
||||
/// </summary>
|
||||
public class QuadTreeManager : IDisposable
|
||||
{
|
||||
private readonly Dictionary<uint, QuadTree> cache = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuadTreeManager"/> class.
|
||||
/// </summary>
|
||||
public QuadTreeManager()
|
||||
{
|
||||
this.cache = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a quad tree for a target.
|
||||
/// </summary>
|
||||
/// <param name="objectID">Target ID.</param>
|
||||
/// <returns>A quadtree instance.</returns>
|
||||
public QuadTree GetQuadTree(uint objectID)
|
||||
{
|
||||
if (!this.cache.TryGetValue(objectID, out var quadTree))
|
||||
{
|
||||
var size = ImGuiHelpers.MainViewport.Size;
|
||||
quadTree = new QuadTree(0, new Rectangle(0, 0, size.X, size.Y));
|
||||
this.cache[objectID] = quadTree;
|
||||
}
|
||||
|
||||
return quadTree;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the quad trees and the cache. This is not the best way, we can do better.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var quadTree in this.cache.Values)
|
||||
{
|
||||
quadTree.Clear();
|
||||
}
|
||||
|
||||
this.cache.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Clear();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Rectangle"/> class.
|
||||
/// </summary>
|
||||
/// <param name="x">X size.</param>
|
||||
/// <param name="y">Y size.</param>
|
||||
/// <param name="width">Width.</param>
|
||||
/// <param name="height">Height.</param>
|
||||
public class Rectangle(float x, float y, float width, float height)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the X.
|
||||
/// </summary>
|
||||
public float X { get; set; } = x;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Y.
|
||||
/// </summary>
|
||||
public float Y { get; set; } = y;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Width.
|
||||
/// </summary>
|
||||
public float Width { get; set; } = width;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Height.
|
||||
/// </summary>
|
||||
public float Height { get; set; } = height;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a point is within a rectangle.
|
||||
/// </summary>
|
||||
/// <param name="point">Point.</param>
|
||||
/// <returns>A bool indicating if a point is within a rectangle.</returns>
|
||||
public bool Contains(Vector2 point)
|
||||
=> point.X >= this.X && point.X < this.X + this.Width &&
|
||||
point.Y >= this.Y && point.Y < this.Y + this.Height;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two rectangles intersect.
|
||||
/// </summary>
|
||||
/// <param name="other">The other rectangle.</param>
|
||||
/// <returns>A bool indicating if they overlap.</returns>
|
||||
public bool Intersects(Rectangle other)
|
||||
=> !(this.X + this.Width < other.X ||
|
||||
this.X > other.X + other.Width ||
|
||||
this.Y + this.Height < other.Y ||
|
||||
this.Y > other.Y + other.Height);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
namespace CBT.Helpers;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using S = System;
|
||||
|
||||
/// <summary>
|
||||
/// ActionManager accesses the Dalamud DataManager.
|
||||
/// </summary>
|
||||
public unsafe class SheetManager : S.IDisposable
|
||||
{
|
||||
private static readonly ExcelSheet<Action>? LuminaActionSheet = Service.DataManager.GetExcelSheet<Action>();
|
||||
private static readonly ExcelSheet<Status>? LuminaStatusSheet = Service.DataManager.GetExcelSheet<Status>();
|
||||
private static readonly ExcelSheet<Item>? LuminaItemSheet = Service.DataManager.GetExcelSheet<Item>();
|
||||
|
||||
private readonly Dictionary<int, Action?> actionCache = [];
|
||||
private readonly Dictionary<int, Status?> statusCache = [];
|
||||
private readonly Dictionary<int, Item?> itemCache = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.actionCache.Clear();
|
||||
this.statusCache.Clear();
|
||||
this.itemCache.Clear();
|
||||
|
||||
S.GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an Icon ID for the given actionID.
|
||||
/// </summary>
|
||||
/// <param name="actionID">Action ID.</param>
|
||||
/// <returns>Icon ID.</returns>
|
||||
public ushort GetIconForAction(int actionID)
|
||||
=> this.actionCache.TryGetValue(actionID, out var action) ? action?.Icon ?? 0 : this.GetActionRow(actionID)?.Icon ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Get an Icon ID for the given value.
|
||||
/// </summary>
|
||||
/// <param name="value1">Status ID.</param>
|
||||
/// <returns>Icon ID.</returns>
|
||||
public uint GetIconForStatus(int value1)
|
||||
=> this.statusCache.TryGetValue(value1, out var status) ? status?.Icon ?? 0 : (this.GetStatusRow(value1)?.Icon ?? 0);
|
||||
|
||||
/// <summary>
|
||||
/// Get an Item ID for the given value.
|
||||
/// </summary>
|
||||
/// <param name="value1">Item ID.</param>
|
||||
/// <returns>Icon ID.</returns>
|
||||
public ushort GetIconForItem(int value1)
|
||||
=> this.itemCache.TryGetValue(value1, out var item) ? item?.Icon ?? 0 : (this.GetItemRow(value1)?.Icon ?? 0);
|
||||
|
||||
/// <summary>
|
||||
/// Get the Ability Name for the given actionID.
|
||||
/// </summary>
|
||||
/// <param name="actionID">Action ID.</param>
|
||||
/// <returns>Ability name.</returns>
|
||||
public ReadOnlySeString GetNameForAction(int actionID)
|
||||
=> this.actionCache.TryGetValue(actionID, out var action) ? action?.Name ?? string.Empty : this.GetActionRow(actionID)?.Name ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Get the Status Name for the given status ID.
|
||||
/// </summary>
|
||||
/// <param name="value1">Status ID.</param>
|
||||
/// <returns>Ability name.</returns>
|
||||
public ReadOnlySeString GetNameForStatus(int value1)
|
||||
=> this.statusCache.TryGetValue(value1, out var status) ? status?.Name ?? string.Empty : this.GetStatusRow(value1)?.Name ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Get the Item Name for the given value.
|
||||
/// </summary>
|
||||
/// <param name="value1">Item ID.</param>
|
||||
/// <returns>Item name.</returns>
|
||||
public ReadOnlySeString GetNameForItem(int value1)
|
||||
=> this.itemCache.TryGetValue(value1, out var item) ? item?.Name ?? string.Empty : this.GetItemRow(value1)?.Name ?? string.Empty;
|
||||
|
||||
private Action? GetActionRow(int actionID)
|
||||
{
|
||||
var row = LuminaActionSheet?.GetRow((uint)actionID);
|
||||
if (row != null)
|
||||
{
|
||||
this.actionCache[actionID] = row;
|
||||
}
|
||||
|
||||
return this.actionCache[actionID];
|
||||
}
|
||||
|
||||
private Status? GetStatusRow(int value1)
|
||||
{
|
||||
var row = LuminaStatusSheet?.GetRow((uint)value1);
|
||||
if (row != null)
|
||||
{
|
||||
this.statusCache[value1] = row;
|
||||
}
|
||||
|
||||
return this.statusCache[value1];
|
||||
}
|
||||
|
||||
private Item? GetItemRow(int value1)
|
||||
{
|
||||
var row = LuminaItemSheet?.GetRow((uint)value1);
|
||||
if (row != null)
|
||||
{
|
||||
this.itemCache[value1] = row;
|
||||
}
|
||||
|
||||
return this.itemCache[value1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
namespace CBT.Interface;
|
||||
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using CBT.FlyText.Configuration;
|
||||
using CBT.Interface.Tabs;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
/// <summary>
|
||||
/// ConfigWindow is the primary configuration GUI for CBT.
|
||||
/// </summary>
|
||||
public partial class ConfigWindow : Window
|
||||
{
|
||||
private readonly Tab tab = new KindTab();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigWindow"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the <see cref="Plugin"/>.</param>
|
||||
public ConfigWindow(string name)
|
||||
: base($"{name} - Cultbaus Battle Text##{name}_CONFIGURATION_WINDOW", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
|
||||
{
|
||||
this.RespectCloseHotkey = true;
|
||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||
this.Size = new Vector2(900, 450);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
using (Service.Fonts.Push(Defaults.DefaultFontName, 14f))
|
||||
{
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(GuiArtist.Scale(5), GuiArtist.Scale(30))))
|
||||
{
|
||||
using (ImRaii.Table("##CONFIGURATION_TABLE", 2, ImGuiTableFlags.BordersInnerV))
|
||||
{
|
||||
ImGui.TableSetupColumn("##LEFT_CONFIG_COLUMN", ImGuiTableColumnFlags.WidthFixed, GuiArtist.Scale(165f));
|
||||
DrawLeftColumn();
|
||||
|
||||
ImGui.TableSetupColumn("##RIGHT_CONFIG_COLUMN", ImGuiTableColumnFlags.WidthStretch);
|
||||
this.DrawRightColumn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnOpen()
|
||||
=> base.OnOpen();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnClose()
|
||||
{
|
||||
this.tab.OnClose();
|
||||
Service.Configuration.Save();
|
||||
}
|
||||
|
||||
private static void DrawLeftColumn()
|
||||
{
|
||||
var regionSize = ImGui.GetContentRegionAvail();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.Child("##LEFT_COLUMN_CHILD", ImGui.GetContentRegionAvail(), false, ImGuiWindowFlags.NoDecoration))
|
||||
{
|
||||
DrawLogo(new Vector2(GuiArtist.Scale(125f), GuiArtist.Scale(125f)));
|
||||
|
||||
// Do not scale this
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.SelectableTextAlign, new Vector2(0.5f, 0.5f)))
|
||||
{
|
||||
// TODO: fixme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawLogo(Vector2 imageSize)
|
||||
{
|
||||
var imagePath = Path.Combine(Service.Interface.AssemblyLocation.DirectoryName!, "Data\\icon.png");
|
||||
var logoImage = Service.TextureProvider.GetFromFile(imagePath).GetWrapOrDefault();
|
||||
|
||||
var regionSize = ImGui.GetContentRegionAvail();
|
||||
|
||||
using (ImRaii.Child("##LOGO", regionSize with { Y = imageSize.Y }, false, ImGuiWindowFlags.NoDecoration))
|
||||
{
|
||||
if (logoImage != null)
|
||||
{
|
||||
var imagePadding = (regionSize.X - imageSize.X) / 2;
|
||||
|
||||
ImGui.SetCursorPosX(imagePadding);
|
||||
ImGui.Image(logoImage.Handle, imageSize);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.Text("You know what it stands for 8^)");
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("Unable to load logo image.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRightColumn()
|
||||
{
|
||||
void DrawContent()
|
||||
{
|
||||
this.tab?.Draw();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
GuiArtist.DrawChildWithMargin("##RIGHT_COLUMN_CHILD", Vector2.Zero, GuiArtist.Scale(5f), DrawContent, ImGuiWindowFlags.None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
namespace CBT.Interface;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CBT.Helpers;
|
||||
using CBT.Types;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using FFXIVClientStructs;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextArtist draws FlyText to the main CBT Canvas.
|
||||
/// </summary>
|
||||
public unsafe class FlyTextArtist
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws events to the CBT canvas.
|
||||
/// </summary>
|
||||
/// <param name="drawList">ImGUI Draw List.</param>
|
||||
/// <param name="flyTextEvents">Events to draw to the canvas.</param>
|
||||
public static void Draw(ImDrawListPtr drawList, List<FlyTextEvent> flyTextEvents)
|
||||
{
|
||||
if (flyTextEvents == null || flyTextEvents.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Service.Tree.Clear();
|
||||
|
||||
flyTextEvents.ForEach(e =>
|
||||
{
|
||||
if (e.Kind != FlyTextKind.None)
|
||||
{
|
||||
var qt = Service.Tree.GetQuadTree(e.Target->GetGameObjectId().ObjectId);
|
||||
|
||||
qt.Insert(e);
|
||||
qt.Retrieve([], e)
|
||||
.ForEach(p =>
|
||||
{
|
||||
if (p != e)
|
||||
{
|
||||
AdjustOverlap(ref e, ref p);
|
||||
}
|
||||
});
|
||||
|
||||
DrawFlyTextWithIconAndOutlines(drawList, ref e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void DrawFlyTextWithIconAndOutlines(ImDrawListPtr drawList, ref FlyTextEvent flyTextEvent)
|
||||
{
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, flyTextEvent.Animation.Alpha))
|
||||
{
|
||||
using (Service.Fonts.Push(flyTextEvent.Config.Font.Name, flyTextEvent.Config.Font.Size))
|
||||
{
|
||||
if (flyTextEvent.Config.Icon.Enabled)
|
||||
{
|
||||
DrawIcon(drawList, ref flyTextEvent);
|
||||
}
|
||||
|
||||
DrawText(drawList, ref flyTextEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawText(ImDrawListPtr drawList, ref FlyTextEvent flyTextEvent)
|
||||
{
|
||||
if (!flyTextEvent.Config.Font.Outline.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawTextOutline(drawList, ref flyTextEvent);
|
||||
|
||||
drawList.AddText(flyTextEvent.Animation.Align(flyTextEvent), ImGui.GetColorU32(flyTextEvent.Color), flyTextEvent.Text);
|
||||
}
|
||||
|
||||
private static void DrawTextOutline(ImDrawListPtr drawList, ref FlyTextEvent flyTextEvent)
|
||||
{
|
||||
static Vector2[] MakeVectors(int i)
|
||||
{
|
||||
return
|
||||
[
|
||||
new Vector2(-i, i),
|
||||
new Vector2(0, i),
|
||||
new Vector2(i, i),
|
||||
new Vector2(-i, 0),
|
||||
new Vector2(i, 0),
|
||||
new Vector2(-i, -i),
|
||||
new Vector2(0, -i),
|
||||
new Vector2(i, -i),
|
||||
];
|
||||
}
|
||||
|
||||
var textPosition = flyTextEvent.Animation.Align(flyTextEvent);
|
||||
var outlineColor = ImGui.GetColorU32(flyTextEvent.Config.Font.Outline.Color);
|
||||
var flyTextMessage = flyTextEvent.Text;
|
||||
|
||||
Enumerable
|
||||
.Range(1, flyTextEvent.Config.Font.Outline.Size)
|
||||
.SelectMany(MakeVectors)
|
||||
.ToList()
|
||||
.ForEach(offset => drawList.AddText(textPosition + offset, outlineColor, flyTextMessage));
|
||||
}
|
||||
|
||||
private static void DrawIcon(ImDrawListPtr drawList, ref FlyTextEvent flyTextEvent)
|
||||
{
|
||||
if (flyTextEvent.Icon == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var textPos = flyTextEvent.Animation.Align(flyTextEvent);
|
||||
var iconConfig = flyTextEvent.Config.Icon;
|
||||
var iconSize = iconConfig.Size;
|
||||
var iconAlpha = (uint)(flyTextEvent.Animation.Alpha * 255.0f) << 24 | 0x00FFFFFF;
|
||||
|
||||
var iconPos = CalculateIconPosition(ref flyTextEvent, ref textPos, ref iconSize);
|
||||
|
||||
if (iconConfig.Outline.Enabled)
|
||||
{
|
||||
DrawIconOutline(drawList, ref flyTextEvent, ref iconPos, ref iconSize);
|
||||
}
|
||||
|
||||
var (uvMin, uvMax) = CalculateUvMinMax(iconConfig.Zoom);
|
||||
|
||||
drawList.AddImage(flyTextEvent.Icon?.Handle ?? 0, iconPos, iconPos + iconSize, uvMin, uvMax, iconAlpha);
|
||||
}
|
||||
|
||||
private static void DrawIconOutline(ImDrawListPtr drawList, ref FlyTextEvent flyTextEvent, ref Vector2 iconPos, ref Vector2 iconSize)
|
||||
{
|
||||
var borderSize = flyTextEvent.Config.Icon.Outline.Size;
|
||||
var borderColor = ImGui.GetColorU32(flyTextEvent.Config.Icon.Outline.Color);
|
||||
var borderMin = new Vector2(iconPos.X - borderSize, iconPos.Y - borderSize);
|
||||
var borderMax = new Vector2(iconPos.X + iconSize.X + borderSize, iconPos.Y + iconSize.Y + borderSize);
|
||||
|
||||
drawList.AddRect(borderMin, borderMax, borderColor, 0.0f, ImDrawFlags.None, borderSize);
|
||||
}
|
||||
|
||||
private static bool IsOverlapping(ref FlyTextEvent a, ref FlyTextEvent b)
|
||||
{
|
||||
var aRect = new Rectangle(a.Position.X, a.Position.Y, a.Size.X, a.Size.Y);
|
||||
var bRect = new Rectangle(b.Position.X, b.Position.Y, b.Size.X, b.Size.Y);
|
||||
|
||||
return aRect.Intersects(bRect);
|
||||
}
|
||||
|
||||
private static float GetOverlap(ref FlyTextEvent a, ref FlyTextEvent b)
|
||||
{
|
||||
var aBottom = a.Position.Y + a.Size.Y;
|
||||
var bBottom = b.Position.Y + b.Size.Y;
|
||||
|
||||
return Math.Max(0, Math.Min(aBottom, bBottom) - Math.Max(a.Position.Y, b.Position.Y)) + 50;
|
||||
}
|
||||
|
||||
private static void AdjustOverlap(ref FlyTextEvent a, ref FlyTextEvent b)
|
||||
{
|
||||
if (!IsOverlapping(ref a, ref b))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (a.Animation.AnimationKind != b.Animation.AnimationKind)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (a.Animation.Reversed != b.Animation.Reversed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(a.Animation.TimeElapsed < b.Animation.TimeElapsed ? a : b).Animation.Offset += new Vector2(0, GetOverlap(ref a, ref b));
|
||||
}
|
||||
|
||||
private static Vector2 CalculateIconPosition(ref FlyTextEvent flyTextEvent, ref Vector2 textPos, ref Vector2 iconSize)
|
||||
{
|
||||
var verticalOffset = (ImGui.CalcTextSize(flyTextEvent.Text).Y - iconSize.Y) / 2.0f;
|
||||
return new Vector2(textPos.X - iconSize.X + flyTextEvent.Config.Icon.Offset.X, textPos.Y + verticalOffset + flyTextEvent.Config.Icon.Offset.Y);
|
||||
}
|
||||
|
||||
private static (Vector2 Min, Vector2 Max) CalculateUvMinMax(float zoom)
|
||||
{
|
||||
var uvOffset = (1.0f - zoom) / 2.0f;
|
||||
return (Min: new Vector2(uvOffset, uvOffset), Max: new Vector2(1.0f - uvOffset, 1.0f - uvOffset));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
namespace CBT.Interface;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using CBT.FlyText.Configuration;
|
||||
using CBT.Interface.Tabs;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
/// <summary>
|
||||
/// Artist is a set of utility methods for drawing ImGui thingies.
|
||||
/// </summary>
|
||||
public class GuiArtist
|
||||
{
|
||||
private const float LongElementWidth = 250f;
|
||||
private const float ShortElementWidth = 50f;
|
||||
|
||||
/// <summary>
|
||||
/// Draws an ImGUI Checkbox.
|
||||
/// </summary>
|
||||
/// <param name="label">Checkbox label.</param>
|
||||
/// <param name="sameLine">Should this element be on the same line.</param>
|
||||
/// <param name="value">Reference to the checkbox state.</param>
|
||||
/// <param name="onChanged">Action to handle changing states.</param>
|
||||
public static void Checkbox(string label, bool sameLine, bool value, Action<bool> onChanged)
|
||||
{
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (ImGui.Checkbox($"##{label}", ref value))
|
||||
{
|
||||
onChanged(value);
|
||||
Service.Configuration.Save();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a selectable label from the Tab state.
|
||||
/// </summary>
|
||||
/// <param name="tab">Tab to draw.</param>
|
||||
/// <param name="sameLine">Should this be on the same line.</param>
|
||||
/// <param name="selected">Whether the tab has been selected.</param>
|
||||
/// <param name="onClicked">What to do when clicked.</param>
|
||||
public static void SelectableTab(KindTab tab, bool sameLine, bool selected, Action<KindTab> onClicked)
|
||||
{
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (ImGui.Selectable($"{tab.Name}##KIND_TAB", selected))
|
||||
{
|
||||
onClicked(tab);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Created a button with the assigned label and on-click handler.
|
||||
/// </summary>
|
||||
/// <param name="label">What the button says.</param>
|
||||
/// <param name="sameLine">Should this be on the same line.</param>
|
||||
/// <param name="onButtonPress">What the button does.</param>
|
||||
public static void Button(string label, bool sameLine, Action onButtonPress)
|
||||
{
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (ImGui.Button(label))
|
||||
{
|
||||
onButtonPress();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a styled button.
|
||||
/// </summary>
|
||||
/// <param name="label">Text label.</param>
|
||||
/// <param name="sameLine">Should this be on the same line.</param>
|
||||
/// <param name="colors">A list of style vars and colors to push.</param>
|
||||
/// <param name="onButtonPress">Action on push.</param>
|
||||
public static void ColoredButton(string label, bool sameLine, List<(ImGuiCol Style, Vector4 Color)> colors, Action onButtonPress)
|
||||
{
|
||||
colors.ForEach(color =>
|
||||
{
|
||||
ImGui.PushStyleColor(color.Style, color.Color);
|
||||
});
|
||||
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
using (Service.Fonts.Push(Defaults.DefaultFontName, 24f))
|
||||
{
|
||||
if (ImGui.Button(label))
|
||||
{
|
||||
onButtonPress();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopStyleColor(colors.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a child with a margin around the content area.
|
||||
/// </summary>
|
||||
/// <param name="childId">String ID for the child.</param>
|
||||
/// <param name="size">Size of the content area.</param>
|
||||
/// <param name="margin">Margin.</param>
|
||||
/// <param name="drawContent">Content action.</param>
|
||||
/// <param name="flags">ImGUI Window Flags.</param>
|
||||
public static void DrawChildWithMargin(string childId, Vector2 size, float margin, Action drawContent, ImGuiWindowFlags flags)
|
||||
{
|
||||
margin = Scale(margin);
|
||||
|
||||
using (ImRaii.Child(childId, size, false))
|
||||
{
|
||||
ImGui.SetCursorPos(new Vector2(margin * 2, margin));
|
||||
|
||||
var regionSize = ImGui.GetContentRegionAvail() - new Vector2(2 * margin, 2 * margin);
|
||||
using (ImRaii.Child($"##{childId}##CONTENT", regionSize, false, flags))
|
||||
{
|
||||
drawContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw Select Picker.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">An enum.</typeparam>
|
||||
/// <param name="label">A label for the select picker.</param>
|
||||
/// <param name="sameLine">Should this be on the same line.</param>
|
||||
/// <param name="currentValue">The current value from which the preview is derived.</param>
|
||||
/// <param name="values">A list of values.</param>
|
||||
/// <param name="action">An action to take with kind T.</param>
|
||||
/// <param name="size">Size of the input width.</param>
|
||||
public static void DrawSelectPicker<T>(string label, bool sameLine, T currentValue, List<T> values, Action<T> action, float size = LongElementWidth)
|
||||
{
|
||||
size = Scale(size);
|
||||
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(size);
|
||||
|
||||
using var combo = ImRaii.Combo($"##{label}", currentValue?.ToString()!);
|
||||
|
||||
if (!combo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
values.ForEach(el =>
|
||||
{
|
||||
if (el != null)
|
||||
{
|
||||
var isSelected = EqualityComparer<T>.Default.Equals(el, currentValue);
|
||||
|
||||
if (ImGui.Selectable(el.ToString(), isSelected))
|
||||
{
|
||||
action(el);
|
||||
}
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
ImGui.SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a color picker.
|
||||
/// </summary>
|
||||
/// <param name="label">String label for the picker.</param>
|
||||
/// <param name="sameLine">Should draw on the same line.</param>
|
||||
/// <param name="currentColor">Current color of the option to change, initial value.</param>
|
||||
/// <param name="action">Action to take with the new color.</param>
|
||||
/// <param name="size">Size of the picker.</param>
|
||||
public static void DrawColorPicker(string label, bool sameLine, Vector4 currentColor, Action<Vector4> action, float size = LongElementWidth)
|
||||
{
|
||||
size = Scale(size);
|
||||
|
||||
var colorPicker = currentColor;
|
||||
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(size);
|
||||
|
||||
if (ImGui.ColorEdit4($"##{label}", ref colorPicker))
|
||||
{
|
||||
action(colorPicker);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a separation.
|
||||
/// </summary>
|
||||
/// <param name="seperatorSize">Size in pixels to draw the space.</param>
|
||||
public static void DrawSeperator(float seperatorSize = 14f)
|
||||
{
|
||||
using (Service.Fonts.Push(Defaults.DefaultFontName, seperatorSize))
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a title.
|
||||
/// </summary>
|
||||
/// <param name="titleText">The text of the title.</param>
|
||||
/// <param name="fontSize">The font size for the title.</param>
|
||||
public static void DrawTitle(string titleText, float fontSize = 24f)
|
||||
{
|
||||
using (Service.Fonts.Push(Defaults.DefaultFontName, fontSize))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(new Vector4(1, 1, 0, 1))))
|
||||
{
|
||||
ImGui.Text(titleText);
|
||||
DrawSeperator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a title.
|
||||
/// </summary>
|
||||
/// <param name="titleText">The text of the title.</param>
|
||||
/// <param name="fontSize">The font size for the title.</param>
|
||||
public static void DrawSubTitle(string titleText, float fontSize = 18f)
|
||||
{
|
||||
using (Service.Fonts.Push(Defaults.DefaultFontName, fontSize))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(new Vector4(0, 1, 1, 1))))
|
||||
{
|
||||
DrawSeperator();
|
||||
ImGui.Text(titleText);
|
||||
DrawSeperator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a warning labe.
|
||||
/// </summary>
|
||||
/// <param name="message">Warning message.</param>
|
||||
/// <param name="fontSize">Size of the warning.</param>
|
||||
public static void DrawWarning(string message, float fontSize = 16f)
|
||||
{
|
||||
using (Service.Fonts.Push(Defaults.DefaultFontName, fontSize))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(new Vector4(1, 0, 0, 1))))
|
||||
{
|
||||
ImGui.Text(message);
|
||||
DrawSeperator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws label prefix. Automatically inlines following element.
|
||||
/// </summary>
|
||||
/// <param name="titleText">The text of the title.</param>
|
||||
/// <param name="sameLine">Should appear on the same line.</param>
|
||||
/// <param name="fontSize">The font size for the title.</param>
|
||||
public static void DrawLabelPrefix(string titleText, bool sameLine, float fontSize = 14f)
|
||||
{
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
using (Service.Fonts.Push(Defaults.DefaultFontName, fontSize))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(new Vector4(1, 1, 0, 1))))
|
||||
{
|
||||
ImGui.Text(titleText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw an input int picker.
|
||||
/// </summary>
|
||||
/// <param name="label">Label for the componenet.</param>
|
||||
/// <param name="sameLine">Should appear on the same line.</param>
|
||||
/// <param name="value">Value to set.</param>
|
||||
/// <param name="min">Min value.</param>
|
||||
/// <param name="max">Max value.</param>
|
||||
/// <param name="onChange">What to do when input changes.</param>
|
||||
public static void DrawInputInt(string label, bool sameLine, int value, int min, int max, Action<int> onChange)
|
||||
{
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(ShortElementWidth * 2);
|
||||
|
||||
if (ImGui.InputInt($"##{label}", ref value, step: 1, stepFast: 2))
|
||||
{
|
||||
value = Math.Clamp(value, min, max);
|
||||
onChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw an input int picker.
|
||||
/// </summary>
|
||||
/// <param name="label">Label for the componenet.</param>
|
||||
/// <param name="sameLine">Should appear on the same line.</param>
|
||||
/// <param name="value">Value to set.</param>
|
||||
/// <param name="min">Min value.</param>
|
||||
/// <param name="max">Max value.</param>
|
||||
/// <param name="onChange">What to do when input changes.</param>
|
||||
public static void DrawInputFloat(string label, bool sameLine, float value, float min, float max, Action<float> onChange)
|
||||
{
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(ShortElementWidth * 2);
|
||||
|
||||
if (ImGui.InputFloat($"##{label}", ref value, step: 1, stepFast: 2))
|
||||
{
|
||||
// value = Math.Clamp(value, min, max);
|
||||
onChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw an input string picker.
|
||||
/// </summary>
|
||||
/// <param name="label">Label for the component.</param>
|
||||
/// <param name="sameLine">Should appear on the same line.</param>
|
||||
/// <param name="value">Input original value.</param>
|
||||
/// <param name="onChange">What to do when input changes.</param>
|
||||
public static void DrawStringInput(string label, bool sameLine, string value, Action<string> onChange)
|
||||
{
|
||||
if (sameLine)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[256];
|
||||
Encoding.UTF8.GetBytes(value, 0, value.Length, buffer, 0);
|
||||
|
||||
ImGui.SetNextItemWidth(Scale(ShortElementWidth * 3));
|
||||
|
||||
if (ImGui.InputText($"##{label}", buffer))
|
||||
{
|
||||
var input = Encoding.UTF8.GetString(buffer).TrimEnd('\0');
|
||||
onChange(input);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale a float based on UI scale.
|
||||
/// </summary>
|
||||
/// <param name="f">Floating value before UI Scale adjustment.</param>
|
||||
/// <returns>Float that has been adjusted for UI scale.</returns>
|
||||
public static float Scale(float f)
|
||||
=> f * ImGuiHelpers.GlobalScale;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
namespace CBT.Interface;
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
/// <summary>
|
||||
/// OverlayWindow is the primary canvas for CBT FlyTextEvents.
|
||||
/// </summary>
|
||||
public class OverlayWindow : Window
|
||||
{
|
||||
private static readonly ImGuiWindowFlags WindowFlags =
|
||||
ImGuiWindowFlags.AlwaysUseWindowPadding
|
||||
| ImGuiWindowFlags.NoBackground
|
||||
| ImGuiWindowFlags.NoFocusOnAppearing
|
||||
| ImGuiWindowFlags.NoInputs
|
||||
| ImGuiWindowFlags.NoScrollbar
|
||||
| ImGuiWindowFlags.NoSavedSettings
|
||||
| ImGuiWindowFlags.NoTitleBar;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OverlayWindow"/> class.
|
||||
/// </summary>
|
||||
public OverlayWindow()
|
||||
: base("CBT Overlay Window##CBT_OVERLAY_WINDOW", WindowFlags, true)
|
||||
{
|
||||
this.IsOpen = true;
|
||||
this.RespectCloseHotkey = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void PreDraw()
|
||||
{
|
||||
base.PreDraw();
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.MainViewport.Size);
|
||||
ImGuiHelpers.SetNextWindowPosRelativeMainViewport(Vector2.Zero);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void PostDraw()
|
||||
{
|
||||
base.PostDraw();
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
Service.Manager.Draw(drawList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
namespace CBT.Interface.Tabs;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// CatgeoryTab configures settings for FlyTextKinds.
|
||||
/// </summary>
|
||||
public class KindTab : Tab
|
||||
{
|
||||
private static readonly List<FlyTextKind> KindPickerValues = [.. FlyTextKindExtension.GetAll().Where(FlyTextKindExtension.UnusedKindPredicate).OrderBy(k => k.ToString())];
|
||||
|
||||
private static FlyTextKind currentKind = FlyTextKindExtension.GetAll().First();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Name => "Configuration";
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override FlyTextKind Current
|
||||
{
|
||||
get => currentKind;
|
||||
set => currentKind = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
GuiArtist.DrawTitle("Kind Configuration Settings");
|
||||
|
||||
this.DrawCurrentConfigurations(KindPickerValues);
|
||||
|
||||
if (this.CurrentEnabled)
|
||||
{
|
||||
this.DrawFontConfigurations();
|
||||
|
||||
if (this.Current.InCategory(FlyTextCategory.AbilityDamage))
|
||||
{
|
||||
this.DrawPositionalPickers();
|
||||
}
|
||||
|
||||
this.DrawIconConfigurations();
|
||||
this.DrawAnimationConfigurations();
|
||||
}
|
||||
|
||||
GuiArtist.DrawSeperator();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnClose()
|
||||
{
|
||||
currentKind = FlyTextKindExtension.GetAll().First();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
namespace CBT.Interface.Tabs;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using CBT.Attributes;
|
||||
using CBT.FlyText.Configuration;
|
||||
using CBT.Types;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
/// <summary>
|
||||
/// Tab class.
|
||||
/// </summary>
|
||||
public abstract class Tab
|
||||
{
|
||||
private static readonly List<string> FontPickerValues = [.. PluginConfiguration.Fonts.Keys];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Name of the window.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current kind.
|
||||
/// </summary>
|
||||
protected abstract FlyTextKind Current { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentEnabled
|
||||
{
|
||||
get => this.GetValue(config => config.Enabled);
|
||||
set => this.SetValue((config, val) => config.Enabled = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentEnabledForSelf
|
||||
{
|
||||
get => this.GetValue(config => config.Filter.Self);
|
||||
set => this.SetValue((config, val) => config.Filter.Self = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentEnabledForEnemy
|
||||
{
|
||||
get => this.GetValue(config => config.Filter.Enemy);
|
||||
set => this.SetValue((config, val) => config.Filter.Enemy = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentEnabledForParty
|
||||
{
|
||||
get => this.GetValue(config => config.Filter.Party);
|
||||
set => this.SetValue((config, val) => config.Filter.Party = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether preview is enabled.
|
||||
/// </summary>
|
||||
protected bool CurrentPreviewEnabled
|
||||
{
|
||||
get => this.GetValue(config => config.PreviewEnabled);
|
||||
set => this.SetValue((config, val) => config.PreviewEnabled = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected string CurrentFont
|
||||
{
|
||||
get => this.GetValue(config => config.Font.Name);
|
||||
set => this.SetValue((config, val) => config.Font.Name = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected Vector4 CurrentFontColor
|
||||
{
|
||||
get => this.GetValue(config => config.Font.Color);
|
||||
set => this.SetValue((config, val) => config.Font.Color = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected float CurrentFontSize
|
||||
{
|
||||
get => this.GetValue(config => config.Font.Size);
|
||||
set => this.SetValue((config, val) => config.Font.Size = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentFontOutlineEnabled
|
||||
{
|
||||
get => this.GetValue(config => config.Font.Outline.Enabled);
|
||||
set => this.SetValue((config, val) => config.Font.Outline.Enabled = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected int CurrentFontOutlineThickness
|
||||
{
|
||||
get => this.GetValue(config => config.Font.Outline.Size);
|
||||
set => this.SetValue((config, val) => config.Font.Outline.Size = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected Vector4 CurrentFontOutlineColor
|
||||
{
|
||||
get => this.GetValue(config => config.Font.Outline.Color);
|
||||
set => this.SetValue((config, val) => config.Font.Outline.Color = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentIconEnabled
|
||||
{
|
||||
get => this.GetValue(config => config.Icon.Enabled);
|
||||
set => this.SetValue((config, val) => config.Icon.Enabled = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected float CurrentIconSize
|
||||
{
|
||||
get => this.GetValue(config => config.Icon.Size.X);
|
||||
set => this.SetValue((config, val) => config.Icon.Size = new Vector2(val, val), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected float CurrentOffsetX
|
||||
{
|
||||
get => this.GetValue(config => config.Offset.X);
|
||||
set => this.SetValue((config, val) => config.Offset = new Vector2(value, config.Offset.Y), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected float CurrentOffsetY
|
||||
{
|
||||
get => this.GetValue(config => config.Offset.Y);
|
||||
set => this.SetValue((config, val) => config.Offset = new Vector2(config.Offset.X, value), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentIconOutlineEnabled
|
||||
{
|
||||
get => this.GetValue(config => config.Icon.Outline.Enabled);
|
||||
set => this.SetValue((config, val) => config.Icon.Outline.Enabled = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected int CurrentIconOutlineThickness
|
||||
{
|
||||
get => this.GetValue(config => config.Icon.Outline.Size);
|
||||
set => this.SetValue((config, val) => config.Icon.Outline.Size = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected Vector4 CurrentIconOutlineColor
|
||||
{
|
||||
get => this.GetValue(config => config.Icon.Outline.Color);
|
||||
set => this.SetValue((config, val) => config.Icon.Outline.Color = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentAnimationReversed
|
||||
{
|
||||
get => this.GetValue(config => config.Animation.Reversed);
|
||||
set => this.SetValue((config, val) => config.Animation.Reversed = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected bool CurrentPositionalsEnabled
|
||||
{
|
||||
get => this.GetValue(config => config.Positionals);
|
||||
set => this.SetValue((config, val) => config.Positionals = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected Vector4 CurrentFontColorSuccess
|
||||
{
|
||||
get => this.GetValue(config => config.Font.ColorSuccess);
|
||||
set => this.SetValue((config, val) => config.Font.ColorSuccess = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether.
|
||||
/// </summary>
|
||||
protected Vector4 CurrentFontColorFailed
|
||||
{
|
||||
get => this.GetValue(config => config.Font.ColorFailed);
|
||||
set => this.SetValue((config, val) => config.Font.ColorFailed = val, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the Window associated with a Tab.
|
||||
/// </summary>
|
||||
public abstract void Draw();
|
||||
|
||||
/// <summary>
|
||||
/// What to do on Close event.
|
||||
/// </summary>
|
||||
public abstract void OnClose();
|
||||
|
||||
/// <summary>
|
||||
/// Get a configuration value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being configured.</typeparam>
|
||||
/// <param name="selector">The means to select the field being retrieved.</param>
|
||||
/// <returns>The value.</returns>
|
||||
protected T GetValue<T>(Func<FlyTextConfiguration, T> selector)
|
||||
{
|
||||
return Service.Configuration.FlyTextKinds.TryGetValue(this.Current, out var currentConfig)
|
||||
? selector(currentConfig)
|
||||
: selector(Service.Configuration.FlyTextKinds[this.Current]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a configuration value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being configured.</typeparam>
|
||||
/// <param name="setter">The means to set the field.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
protected void SetValue<T>(Action<FlyTextConfiguration, T> setter, T value)
|
||||
{
|
||||
if (!Service.Configuration.FlyTextKinds.TryGetValue(this.Current, out var currentConfig))
|
||||
{
|
||||
currentConfig = new FlyTextConfiguration(Service.Configuration.FlyTextKinds[this.Current]);
|
||||
Service.Configuration.FlyTextKinds[this.Current] = currentConfig;
|
||||
}
|
||||
|
||||
setter(currentConfig, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw configuration options current elements.
|
||||
/// </summary>
|
||||
/// <param name="allKinds">All kinds to be configured. Can be groups, categories, or kinds.</param>
|
||||
protected void DrawCurrentConfigurations(List<FlyTextKind> allKinds)
|
||||
{
|
||||
GuiArtist.DrawSubTitle($"{this.Name} Configurations");
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix($"Select {this.Name}", sameLine: false);
|
||||
GuiArtist.DrawSelectPicker($"Select_{this.Name}", sameLine: true, this.Current, allKinds, category => { this.Current = category; });
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Enabled", sameLine: false);
|
||||
GuiArtist.Checkbox($"Enabled_{this.Name}", sameLine: true, this.CurrentEnabled, enabled => { this.CurrentEnabled = enabled; });
|
||||
|
||||
if (this.CurrentEnabled)
|
||||
{
|
||||
if (Service.Configuration.FlyTextKinds.TryGetValue(this.Current, out var currentConfig))
|
||||
{
|
||||
if (this.Current.ShouldAllow(FlyTextFilter.Self))
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Enabled On Self", sameLine: false);
|
||||
GuiArtist.Checkbox($"Enabled On Self_{this.Name}", sameLine: true, this.CurrentEnabledForSelf, enabled => { this.CurrentEnabledForSelf = enabled; });
|
||||
}
|
||||
|
||||
if (this.Current.ShouldAllow(FlyTextFilter.Enemy))
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Enabled On Enemy", sameLine: false);
|
||||
GuiArtist.Checkbox($"Enabled On Enemy_{this.Name}", sameLine: true, this.CurrentEnabledForEnemy, enabled => { this.CurrentEnabledForEnemy = enabled; });
|
||||
}
|
||||
|
||||
if (this.Current.ShouldAllow(FlyTextFilter.Party))
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Enabled On Party", sameLine: false);
|
||||
GuiArtist.Checkbox($"Enabled On Party_{this.Name}", sameLine: true, this.CurrentEnabledForParty, enabled => { this.CurrentEnabledForParty = enabled; });
|
||||
}
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Preview", sameLine: false);
|
||||
GuiArtist.Checkbox($"Enabled On Party_{this.Name}", sameLine: true, this.CurrentPreviewEnabled, enabled => { this.CurrentPreviewEnabled = enabled; });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw font configurations.
|
||||
/// </summary>
|
||||
protected void DrawFontConfigurations()
|
||||
{
|
||||
GuiArtist.DrawSubTitle("Font Configurations");
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Select Font", sameLine: false);
|
||||
GuiArtist.DrawSelectPicker($"Font_{this.Name}", sameLine: true, this.CurrentFont, FontPickerValues, font => { this.CurrentFont = font; });
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Select Font Size", sameLine: false);
|
||||
GuiArtist.DrawSelectPicker($"Font Size_{this.Name}", sameLine: true, this.CurrentFontSize, PluginConfiguration.Fonts[this.CurrentFont], size => { this.CurrentFontSize = size; }, 50f);
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Select Font Color", sameLine: false);
|
||||
GuiArtist.DrawColorPicker($"Font Color_{this.Name}", sameLine: true, this.CurrentFontColor, color => { this.CurrentFontColor = color; });
|
||||
|
||||
GuiArtist.DrawSubTitle("Font Outline Configurations");
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Enable Font Outline", sameLine: false);
|
||||
GuiArtist.Checkbox($"Font Outline_{this.Name}", sameLine: true, this.CurrentFontOutlineEnabled, enabled => { this.CurrentFontOutlineEnabled = enabled; });
|
||||
|
||||
if (this.CurrentFontOutlineEnabled)
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Select Font Outline Thickness", sameLine: false);
|
||||
GuiArtist.DrawInputInt($"Font Outline Thickness_{this.Name}", sameLine: true, this.CurrentFontOutlineThickness, 1, 5, thickness => { this.CurrentFontOutlineThickness = thickness; });
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Select Font Outline Color", sameLine: false);
|
||||
GuiArtist.DrawColorPicker($"Font Outline Color_{this.Name}", sameLine: true, this.CurrentFontOutlineColor, color => { this.CurrentFontOutlineColor = color; });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw icon configurations.
|
||||
/// </summary>
|
||||
protected void DrawIconConfigurations()
|
||||
{
|
||||
GuiArtist.DrawSubTitle("Icon Configurations");
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Enable Icon", sameLine: false);
|
||||
GuiArtist.Checkbox($"Enable Icon_{this.Name}", sameLine: true, this.CurrentIconEnabled, enabled => { this.CurrentIconEnabled = enabled; });
|
||||
|
||||
if (this.CurrentIconEnabled)
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Select Icon Size", sameLine: false);
|
||||
GuiArtist.DrawInputInt($"Icon Size_{this.Name}", sameLine: true, (int)this.CurrentIconSize, 14, 32, size => { this.CurrentIconSize = size; });
|
||||
|
||||
GuiArtist.DrawSubTitle("Icon Outline Configurations");
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Enable Icon Outline", sameLine: false);
|
||||
GuiArtist.Checkbox($"Icon Outline_{this.Name}", sameLine: true, this.CurrentIconOutlineEnabled, enabled => { this.CurrentIconOutlineEnabled = enabled; });
|
||||
|
||||
if (this.CurrentIconOutlineEnabled)
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Select Icon Outline Thickness", sameLine: false);
|
||||
GuiArtist.DrawInputInt($"Icon Outline Thickness_{this.Name}", sameLine: true, this.CurrentIconOutlineThickness, 1, 5, thickness => { this.CurrentIconOutlineThickness = thickness; });
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Select Icon Outline Color", sameLine: false);
|
||||
GuiArtist.DrawColorPicker($"Ocn Outline Color_{this.Name}", sameLine: true, this.CurrentIconOutlineColor, color => { this.CurrentIconOutlineColor = color; });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw animation configurations.
|
||||
/// </summary>
|
||||
protected void DrawAnimationConfigurations()
|
||||
{
|
||||
var size = ImGuiHelpers.MainViewport.Size;
|
||||
|
||||
GuiArtist.DrawSubTitle("Animation Configuration");
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Reverse Animation", sameLine: false);
|
||||
GuiArtist.Checkbox($"Reverse Animation_{this.Name}", sameLine: true, this.CurrentAnimationReversed, enabled => { this.CurrentAnimationReversed = enabled; });
|
||||
|
||||
GuiArtist.DrawLabelPrefix("X Offset", sameLine: false);
|
||||
GuiArtist.DrawInputFloat($"X Offset_{this.Name}", sameLine: true, this.CurrentOffsetX, 0, size.X, size => { this.CurrentOffsetX = size; });
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Y Offset", sameLine: false);
|
||||
GuiArtist.DrawInputFloat($"Y Offset_{this.Name}", sameLine: true, this.CurrentOffsetY, 0, size.Y, size => { this.CurrentOffsetY = size; });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw positional color pickers.
|
||||
/// </summary>
|
||||
protected void DrawPositionalPickers()
|
||||
{
|
||||
GuiArtist.DrawSubTitle("Positional Color Configuration");
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Enable Positional Recolor", sameLine: false);
|
||||
GuiArtist.Checkbox($"Enable Positional Recolor_{this.Name}", sameLine: true, this.CurrentPositionalsEnabled, enabled => { this.CurrentPositionalsEnabled = enabled; });
|
||||
|
||||
if (this.CurrentPositionalsEnabled)
|
||||
{
|
||||
GuiArtist.DrawLabelPrefix("Select Positional Success Color", sameLine: false);
|
||||
GuiArtist.DrawColorPicker($"Font Positional Success Color_{this.Name}", sameLine: true, this.CurrentFontColorSuccess, color => { this.CurrentFontColorSuccess = color; });
|
||||
|
||||
GuiArtist.DrawLabelPrefix("Select Positional Failed Color", sameLine: false);
|
||||
GuiArtist.DrawColorPicker($"Font Positional Failed Color_{this.Name}", sameLine: true, this.CurrentFontColorFailed, color => { this.CurrentFontColorFailed = color; });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
namespace CBT;
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using CBT.FlyText;
|
||||
using CBT.Helpers;
|
||||
using CBT.Interface;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin is the main entrypoint for CBT.
|
||||
/// </summary>
|
||||
public sealed partial class Plugin : IDalamudPlugin
|
||||
{
|
||||
private static readonly string Command = "/cbt";
|
||||
|
||||
private static readonly string Name = "CBT";
|
||||
|
||||
private readonly WindowSystem windowSystem;
|
||||
|
||||
private readonly ConfigWindow configWindow;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pluginInterface">Dalamud plugin interface.</param>
|
||||
/// <param name="sigScanner">Dalamus signature scanner.</param>
|
||||
/// <param name="gameInteropProvider">Dalamud game interop provider.</param>
|
||||
public Plugin(IDalamudPluginInterface pluginInterface, ISigScanner sigScanner, IGameInteropProvider gameInteropProvider)
|
||||
{
|
||||
pluginInterface.Create<Service>();
|
||||
|
||||
var overlayWindow = new OverlayWindow();
|
||||
var artist = new FlyTextArtist();
|
||||
var assemblyLocation = Service.Interface.AssemblyLocation.DirectoryName != null
|
||||
? Service.Interface.AssemblyLocation.DirectoryName + "\\"
|
||||
: Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
this.configWindow = new ConfigWindow(Name);
|
||||
this.windowSystem = new WindowSystem(Name);
|
||||
|
||||
this.windowSystem.AddWindow(this.configWindow);
|
||||
this.windowSystem.AddWindow(overlayWindow);
|
||||
|
||||
Service.Address = new PluginAddressResolver();
|
||||
Service.Address.Setup(sigScanner);
|
||||
Service.CommandManager.AddHandler(Command, new CommandInfo(this.OnCommand)
|
||||
{
|
||||
HelpMessage = "Open a window to edit CBT settings.",
|
||||
ShowInHelp = true,
|
||||
});
|
||||
Service.Configuration = pluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
|
||||
Service.Fonts = new FontManager(Path.GetDirectoryName(assemblyLocation) + "\\Media\\Fonts\\");
|
||||
Service.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
||||
Service.Interface.UiBuilder.OpenMainUi += this.OnOpenConfigUi;
|
||||
Service.Interface.UiBuilder.Draw += this.windowSystem.Draw;
|
||||
Service.Manager = new PluginManager();
|
||||
Service.Manifest = new ManifestManager(assemblyLocation + Path.GetFileNameWithoutExtension(Service.Interface.AssemblyLocation.FullName) + ".json");
|
||||
Service.Receiver = new FlyTextReceiver(gameInteropProvider);
|
||||
Service.Sheet = new SheetManager();
|
||||
Service.Tree = new QuadTreeManager();
|
||||
Service.Pool = new FlyTextPool();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Service.Pool.Dispose();
|
||||
Service.Tree.Dispose();
|
||||
Service.Sheet.Dispose();
|
||||
Service.Receiver.Dispose();
|
||||
Service.Manager.Dispose();
|
||||
Service.Manifest.Dispose();
|
||||
Service.Fonts.Dispose();
|
||||
Service.Interface.UiBuilder.Draw -= this.windowSystem.Draw;
|
||||
Service.Interface.UiBuilder.OpenMainUi -= this.OnOpenConfigUi;
|
||||
Service.Interface.UiBuilder.OpenConfigUi -= this.OnOpenConfigUi;
|
||||
Service.CommandManager.RemoveHandler(Command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the state of the configuration window when opened.
|
||||
/// </summary>
|
||||
private void OnOpenConfigUi()
|
||||
=> this.configWindow.IsOpen = true;
|
||||
|
||||
/// <summary>
|
||||
/// OnCommand reacts to commands received via <see cref="Command"/>.
|
||||
/// </summary>
|
||||
/// <param name="command">The command received.</param>
|
||||
/// <param name="arguments">Arguments received alongside the command.</param>
|
||||
private void OnCommand(string command, string arguments)
|
||||
=> this.configWindow.Toggle();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace CBT;
|
||||
|
||||
using System;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Dalamud address resolver.
|
||||
/// </summary>
|
||||
public class PluginAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of AddScreenLogWithKind.
|
||||
/// </summary>
|
||||
public IntPtr AddScreenLogWithKind { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of AddScreenLog.
|
||||
/// </summary>
|
||||
public IntPtr AddScreenLog { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of ReceiveActionEffect.
|
||||
/// </summary>
|
||||
public IntPtr ReceiveActionEffect { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner scanner)
|
||||
{
|
||||
this.AddScreenLogWithKind = scanner.ScanText("E8 ?? ?? ?? ?? BF ?? ?? ?? ?? EB 39");
|
||||
this.AddScreenLog = scanner.ScanText("E8 ?? ?? ?? ?? 45 85 E4 0F 84 ?? ?? ?? ?? 48 8B 0D");
|
||||
this.ReceiveActionEffect = scanner.ScanText("40 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24");
|
||||
|
||||
Service.PluginLog.Debug($"{nameof(this.AddScreenLogWithKind)} 0x{this.AddScreenLogWithKind:X}");
|
||||
Service.PluginLog.Debug($"{nameof(this.AddScreenLog)} 0x{this.AddScreenLog:X}");
|
||||
Service.PluginLog.Debug($"{nameof(this.ReceiveActionEffect)} 0x{this.ReceiveActionEffect:X}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
namespace CBT;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CBT.Attributes;
|
||||
using CBT.FlyText.Configuration;
|
||||
using CBT.Interface.Tabs;
|
||||
using CBT.Types;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
/// <summary>
|
||||
/// Global options.
|
||||
/// </summary>
|
||||
public enum GlobalOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays native FlyText for unhandled Kinds.
|
||||
/// </summary>
|
||||
NativeUnhandled,
|
||||
|
||||
/// <summary>
|
||||
/// Unhook the anchor from the local player.
|
||||
/// </summary>
|
||||
PlayerAnchor,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dalamud plugin configuration implementation.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PluginConfiguration : IPluginConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
|
||||
/// </summary>
|
||||
public PluginConfiguration()
|
||||
{
|
||||
this.Options.Add(GlobalOption.NativeUnhandled.ToString(), false);
|
||||
this.Options.Add(GlobalOption.PlayerAnchor.ToString(), true);
|
||||
|
||||
var size = ImGui.GetMainViewport().Size;
|
||||
this.FreeMoveAnchor = new Vector2(size.X / 2, size.Y / 2);
|
||||
|
||||
this.FlyTextKinds = FlyTextKindExtension
|
||||
.GetAll()
|
||||
.ToDictionary(
|
||||
kind => kind,
|
||||
kind => new FlyTextConfiguration());
|
||||
|
||||
this.FlyTextCategories = FlyTextCategoryExtension
|
||||
.GetAllCategories()
|
||||
.ToDictionary(
|
||||
kind => kind,
|
||||
kind => new FlyTextConfiguration());
|
||||
|
||||
this.FlyTextGroups = FlyTextCategoryExtension
|
||||
.GetAllGroups()
|
||||
.ToDictionary(
|
||||
kind => kind,
|
||||
kind => new FlyTextConfiguration());
|
||||
|
||||
FlyTextCategory.AbilityDamage
|
||||
.ForEachKind(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Font.Color = new Vector4(1, 1, 0, 1);
|
||||
this.FlyTextKinds[kind].Font.Size = 24f;
|
||||
|
||||
this.FlyTextKinds[kind].Positionals = true;
|
||||
this.FlyTextKinds[kind].Font.ColorSuccess = new Vector4(0.4f, 1, 0.4f, 1);
|
||||
this.FlyTextKinds[kind].Font.ColorFailed = new Vector4(1, 0.4f, 0.4f, 1);
|
||||
});
|
||||
|
||||
FlyTextCategory.AutoAttack
|
||||
.ForEachKind(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Font.Color = new Vector4(1, 1, 1, 1);
|
||||
this.FlyTextKinds[kind].Font.Size = 18f;
|
||||
});
|
||||
|
||||
FlyTextCategory.AbilityHealing
|
||||
.ForEachKind(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Font.Color = new Vector4(0, 1, 0, 1);
|
||||
this.FlyTextKinds[kind].Font.Size = 18f;
|
||||
});
|
||||
|
||||
FlyTextCategory.Miss
|
||||
.ForEachKind(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Font.Color = new Vector4(1, 1, 1, 1);
|
||||
this.FlyTextKinds[kind].Font.Size = 18f;
|
||||
});
|
||||
|
||||
FlyTextCategory.NonCombat
|
||||
.ForEachKind(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Enabled = false;
|
||||
});
|
||||
|
||||
FlyTextCategory.NonCombat
|
||||
.ForEachKind(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Enabled = false;
|
||||
});
|
||||
|
||||
FlyTextCategory.Buff
|
||||
.ForEachKind(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Font.Color = new Vector4(0.4f, 1, 0.4f, 1);
|
||||
this.FlyTextKinds[kind].Font.Size = 18f;
|
||||
this.FlyTextKinds[kind].Animation.Alignment = FlyText.Animations.FlyTextAlignment.Left;
|
||||
this.FlyTextKinds[kind].Message.Prefix = "+";
|
||||
});
|
||||
|
||||
FlyTextCategory.Buff
|
||||
.Where(kind => kind == FlyTextKind.DebuffFading || kind == FlyTextKind.BuffFading)
|
||||
.ToList()
|
||||
.ForEach(kind =>
|
||||
{
|
||||
this.FlyTextKinds[kind].Animation.Reversed = true;
|
||||
this.FlyTextKinds[kind].Font.Size = 18f;
|
||||
this.FlyTextKinds[kind].Font.Color = new Vector4(1, 0.4f, 0.4f, 1);
|
||||
this.FlyTextKinds[kind].Message.Prefix = "-";
|
||||
});
|
||||
|
||||
FlyTextKindExtension
|
||||
.GetAll()
|
||||
.ToList()
|
||||
.ForEach(kind =>
|
||||
{
|
||||
if (kind.ShouldFilter(FlyTextFilter.Party))
|
||||
{
|
||||
this.FlyTextKinds[kind].Filter.Party = false;
|
||||
}
|
||||
|
||||
if (kind.ShouldFilter(FlyTextFilter.Enemy))
|
||||
{
|
||||
this.FlyTextKinds[kind].Filter.Enemy = false;
|
||||
}
|
||||
|
||||
if (kind.ShouldFilter(FlyTextFilter.Self))
|
||||
{
|
||||
this.FlyTextKinds[kind].Filter.Self = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Fonts configuration settings.
|
||||
/// </summary>
|
||||
public static Dictionary<string, List<float>> Fonts { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FlyTextKinds Configuration options.
|
||||
/// </summary>
|
||||
public Dictionary<FlyTextKind, FlyTextConfiguration> FlyTextKinds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FlyTextCategory Category Configuration options.
|
||||
/// </summary>
|
||||
public Dictionary<FlyTextCategory, FlyTextConfiguration> FlyTextCategories { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FlyTextCategory Group Configuration options.
|
||||
/// </summary>
|
||||
public Dictionary<FlyTextCategory, FlyTextConfiguration> FlyTextGroups { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plugin options.
|
||||
/// </summary>
|
||||
public Dictionary<string, bool> Options { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the free move anchor.
|
||||
/// </summary>
|
||||
public Vector2 FreeMoveAnchor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Configuration Version.
|
||||
/// </summary>
|
||||
public int Version { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Persist the configuration settings to disk.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
=> Service.Interface.SavePluginConfig(this);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
namespace CBT;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CBT.FlyText.Configuration;
|
||||
using CBT.Interface;
|
||||
using CBT.Types;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
/// <summary>
|
||||
/// PluginManager instance.
|
||||
/// </summary>
|
||||
public unsafe partial class PluginManager : IDisposable
|
||||
{
|
||||
private readonly List<FlyTextEvent> eventStream = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginManager"/> class.
|
||||
/// </summary>
|
||||
public PluginManager()
|
||||
{
|
||||
Service.Framework.Update += this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of unhandled FlyTextEvents lingering in the stream.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.eventStream?.Clear();
|
||||
|
||||
Service.Framework.Update -= this.FrameworkUpdate;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a FlyTextEvent to the event stream.
|
||||
/// </summary>
|
||||
/// <param name="flyTextEvent">A FlyText event.</param>
|
||||
public void Add(FlyTextEvent flyTextEvent)
|
||||
{
|
||||
this.eventStream?.Add(flyTextEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a FlyTextEvent to the Canvas.
|
||||
/// </summary>
|
||||
/// <param name="drawList">ImGui DrawList pointer.</param>
|
||||
public void Draw(ImDrawListPtr drawList)
|
||||
{
|
||||
FlyTextArtist.Draw(drawList, this.eventStream);
|
||||
}
|
||||
|
||||
private void FrameworkUpdate(IFramework framework)
|
||||
{
|
||||
if (this.eventStream.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var expiredEvents = new List<FlyTextEvent>();
|
||||
|
||||
this.eventStream?.ForEach(e =>
|
||||
{
|
||||
e.Update(framework.UpdateDelta.Milliseconds);
|
||||
|
||||
if (e.IsExpired)
|
||||
{
|
||||
expiredEvents.Add(e);
|
||||
}
|
||||
});
|
||||
expiredEvents.ForEach(e =>
|
||||
{
|
||||
this.eventStream?.Remove(e);
|
||||
|
||||
Service.Pool.Put(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextReceiver static member partial class implementation.
|
||||
/// </summary>
|
||||
public unsafe partial class PluginManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Local Player from the Dalamud object table.
|
||||
/// </summary>
|
||||
public static IPlayerCharacter? LocalPlayer
|
||||
=> Service.ObjectTable.LocalPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Get the config for the kind.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind to query config for.</param>
|
||||
/// <returns>The configuration options.</returns>
|
||||
public static FlyTextConfiguration? GetConfigForKind(FlyTextKind kind)
|
||||
=> Service.Configuration.FlyTextKinds.TryGetValue(kind, out var config) ? config : null;
|
||||
|
||||
/// <summary>
|
||||
/// Is the target an enemy.
|
||||
/// </summary>
|
||||
/// <param name="target">Target of the event.</param>
|
||||
/// <returns>A bool if they're a bad guy.</returns>
|
||||
public static bool IsEnemy(Character* target)
|
||||
=> target->ObjectKind == ObjectKind.BattleNpc && target->SubKind == 5;
|
||||
|
||||
/// <summary>
|
||||
/// Is the source a party member.
|
||||
/// </summary>
|
||||
/// <param name="source">Target of the event.</param>
|
||||
/// <returns>Are they in our party.</returns>
|
||||
public static bool IsPartyMember(Character* source)
|
||||
=> source->IsPartyMember;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a character instance is the player character.
|
||||
/// </summary>
|
||||
/// <param name="character">Character instance.</param>
|
||||
/// <returns>A boolean which is true if the Character is the player.</returns>
|
||||
public static bool IsPlayerCharacter(Character* character)
|
||||
=> LocalPlayer?.GameObjectId == character->GetGameObjectId().ObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Involves events for which the player is the source or the target.
|
||||
/// </summary>
|
||||
/// <param name="source">Source which an action originated from.</param>
|
||||
/// <param name="target">Target of the action.</param>
|
||||
/// <returns>True if the target or source is the player character.</returns>
|
||||
public static bool InvolvesMe(Character* source, Character* target)
|
||||
=> IsPlayerCharacter(source) || IsPlayerCharacter(target);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if an event is unfiltered.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind of the event.</param>
|
||||
/// <returns>True if the event is enabled.</returns>
|
||||
public static bool Unfiltered(FlyTextKind kind)
|
||||
=> Service.Configuration.FlyTextKinds[kind].Enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance from the player to the target.
|
||||
/// </summary>
|
||||
/// <param name="target">Target of the FlyTextEvent.</param>
|
||||
/// <returns>The Yalm Distance from the target to the player.</returns>
|
||||
public static double GetDistance(Character* target)
|
||||
{
|
||||
if (target is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var x = target->YalmDistanceFromPlayerX;
|
||||
var y = target->YalmDistanceFromPlayerZ;
|
||||
|
||||
return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2));
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
namespace CBT;
|
||||
|
||||
using CBT.FlyText;
|
||||
using CBT.Helpers;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Dalamud plugin services.
|
||||
/// </summary>
|
||||
public class Service
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Dalamud plugin configuration.
|
||||
/// </summary>
|
||||
public static PluginConfiguration Configuration { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Dalamud plugin address resolver.
|
||||
/// </summary>
|
||||
public static PluginAddressResolver Address { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CBT Plugin manager.
|
||||
/// </summary>
|
||||
public static PluginManager Manager { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CBT FlyText receiver.
|
||||
/// </summary>
|
||||
public static FlyTextReceiver Receiver { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CBT Font manager.
|
||||
/// </summary>
|
||||
public static FontManager Fonts { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CBT Manifest manager.
|
||||
/// </summary>
|
||||
public static ManifestManager Manifest { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CBT Ability manager.
|
||||
/// </summary>
|
||||
public static SheetManager Sheet { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CBT QuadTree manager.
|
||||
/// </summary>
|
||||
public static QuadTreeManager Tree { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CBT FlyText Pool.
|
||||
/// </summary>
|
||||
public static FlyTextPool Pool { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud ClientState.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IClientState ClientState { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud CommandManager.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static ICommandManager CommandManager { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud Plugin Interface.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IDalamudPluginInterface Interface { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud Data Manager.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IDataManager DataManager { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud FlyTextGui.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IFlyTextGui FlyTextGui { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud Framework.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IFramework Framework { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud GameGui.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IGameGui GameGui { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud ObjectTable.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IObjectTable ObjectTable { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud PluginLog.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static IPluginLog PluginLog { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud TargetManager.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static ITargetManager TargetManager { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud TextureProvider.
|
||||
/// </summary>
|
||||
[PluginService]
|
||||
public static ITextureProvider TextureProvider { get; private set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// ActionEventKind are derived from the Action Event Handler on the target of a FlyTextEvent.
|
||||
/// This is, clearly, not exhaustive.
|
||||
/// </summary>
|
||||
public enum ActionEventKind : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// None.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Damage event. Applies to both abilities and auto attacks. Has some additional parameters which coincide with criticals/dh/dhc.
|
||||
/// </summary>
|
||||
Damage = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Healing events, includes HpDrain. Has some additional parameters which coincide with criticals/dh/dhc.
|
||||
/// </summary>
|
||||
Healing = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Mp regen ticks.
|
||||
/// </summary>
|
||||
MpGain = 11,
|
||||
|
||||
/// <summary>
|
||||
/// PlayerAppliedDebuff - maybe. Some ambiguity around this.
|
||||
/// </summary>
|
||||
PlayerAppliedDebuff = 14,
|
||||
|
||||
/// <summary>
|
||||
/// PlayerAppliedBuff - maybe. Some ambiguity around this.
|
||||
/// </summary>
|
||||
PlayerAppliedBuff = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Some sort of combo action. Has some additional parameters which coincide with combo state.
|
||||
/// </summary>
|
||||
ComboAction = 27,
|
||||
|
||||
/// <summary>
|
||||
/// Changes to the state of the Job Gauge.
|
||||
/// </summary>
|
||||
JobGauge = 62,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace CBT.Types;
|
||||
|
||||
/// <summary>
|
||||
/// DamageKind is the type of damage.
|
||||
/// </summary>
|
||||
public enum DamageKind
|
||||
{
|
||||
/// <summary>
|
||||
/// All physical damage types.
|
||||
/// </summary>
|
||||
Physical = 1,
|
||||
|
||||
/// <summary>
|
||||
/// All magical damage types.
|
||||
/// </summary>
|
||||
Magical = 5,
|
||||
|
||||
/// <summary>
|
||||
/// All unique or 'dark' damage types.
|
||||
/// </summary>
|
||||
Unique = 6,
|
||||
|
||||
/// <summary>
|
||||
/// All limit break damage types.
|
||||
/// </summary>
|
||||
LimitBreak = 8,
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
namespace CBT.Types;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextCategory is a collect of Groups and Categories which organize a subset of <see cref="FlyTextKind"/>s.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum FlyTextCategory
|
||||
{
|
||||
// Groups
|
||||
|
||||
/// <summary>
|
||||
/// Combat group.
|
||||
/// </summary>
|
||||
Combat = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Non-combat group.
|
||||
/// </summary>
|
||||
NonCombat = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// None group.
|
||||
/// </summary>
|
||||
GroupNone = 1 << 2,
|
||||
|
||||
// Categories
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack category. Member of the Combat group.
|
||||
/// </summary>
|
||||
AutoAttack = 1 << 10 | Combat,
|
||||
|
||||
/// <summary>
|
||||
/// AbilityDamage category. Member of the Combat group.
|
||||
/// </summary>
|
||||
AbilityDamage = 1 << 11 | Combat,
|
||||
|
||||
/// <summary>
|
||||
/// AbilityHealing category. Member of the Combat group.
|
||||
/// </summary>
|
||||
AbilityHealing = 1 << 12 | Combat,
|
||||
|
||||
/// <summary>
|
||||
/// Miss category. Member of the Combat group.
|
||||
/// </summary>
|
||||
Miss = 1 << 13 | Combat,
|
||||
|
||||
/// <summary>
|
||||
/// Buff category. Member of the Combat group.
|
||||
/// </summary>
|
||||
Buff = 1 << 14 | Combat,
|
||||
|
||||
/// <summary>
|
||||
/// Debuff category. Member of the Combat group.
|
||||
/// </summary>
|
||||
Debuff = 1 << 15 | Combat,
|
||||
|
||||
/// <summary>
|
||||
/// CC category. Member of the Combat group.
|
||||
/// </summary>
|
||||
CC = 1 << 16 | Combat,
|
||||
|
||||
/// <summary>
|
||||
/// Non-combat events. Crafting, Gp Gains, etc.
|
||||
/// </summary>
|
||||
Other = 1 << 17 | NonCombat,
|
||||
|
||||
/// <summary>
|
||||
/// CC category. Member of the Combat group.
|
||||
/// </summary>
|
||||
CategoryNone = 1 << 17 | GroupNone,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextCategory methods.
|
||||
/// </summary>
|
||||
public static class FlyTextCategoryExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if a FlyTextCategory is a Group sub-type.
|
||||
/// </summary>
|
||||
/// <param name="value">FlyTextCategory to check.</param>
|
||||
/// <returns>True if the category is a group sub-type.</returns>
|
||||
public static bool IsGroup(this FlyTextCategory value)
|
||||
=> value == FlyTextCategory.Combat || value == FlyTextCategory.NonCombat;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a FlyTextCategory is a Category sub-type.
|
||||
/// </summary>
|
||||
/// <param name="value">FlyTextCategory to check.</param>
|
||||
/// <returns>True if the category is a category sub-type.</returns>
|
||||
public static bool IsCategory(this FlyTextCategory value)
|
||||
=> !IsGroup(value);
|
||||
|
||||
/// <summary>
|
||||
/// Get kinds for a category or group.
|
||||
/// </summary>
|
||||
/// <param name="category">Category or group.</param>
|
||||
/// <returns>Enumerable result of kinds in that category or group.</returns>
|
||||
public static IEnumerable<FlyTextKind> GetKindsFor(FlyTextCategory category)
|
||||
=> Enum.GetValues<FlyTextKind>()
|
||||
.Cast<FlyTextKind>()
|
||||
.Where(kind => IsCategory(category) ? kind.InCategory(category) : kind.InGroup(category));
|
||||
|
||||
/// <summary>
|
||||
/// Get categories for a group.
|
||||
/// </summary>
|
||||
/// <param name="group">The group to iterate.</param>
|
||||
/// <returns>Enumerable result of categories in that group.</returns>
|
||||
public static IEnumerable<FlyTextCategory> GetCategoriesFor(FlyTextCategory group)
|
||||
=> Enum.GetValues<FlyTextCategory>()
|
||||
.Cast<FlyTextCategory>()
|
||||
.Where(category => category.IsCategory() && category.HasFlag(group));
|
||||
|
||||
/// <summary>
|
||||
/// Implements a filter over fly text categories.
|
||||
/// </summary>
|
||||
/// <param name="category">The category to filter over.</param>
|
||||
/// <param name="predicate">The predicate to filter by.</param>
|
||||
/// <returns>The collection of fly text kinds which have been filtered.</returns>
|
||||
public static IEnumerable<FlyTextKind> Where(this FlyTextCategory category, Predicate<FlyTextKind> predicate)
|
||||
=> GetKindsFor(category)
|
||||
.Where(kind => predicate(kind));
|
||||
|
||||
/// <summary>
|
||||
/// Iterates the kinds of a category or group and performs an action.
|
||||
/// </summary>
|
||||
/// <param name="category">The category or group to iterate.</param>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public static void ForEachKind(this FlyTextCategory category, Action<FlyTextKind> action)
|
||||
{
|
||||
GetKindsFor(category)
|
||||
.ToList()
|
||||
.ForEach(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates the kinds of a category or group and performs an action.
|
||||
/// </summary>
|
||||
/// <param name="category">The category or group to iterate.</param>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public static void ForEachCategory(this FlyTextCategory category, Action<FlyTextCategory> action)
|
||||
{
|
||||
GetCategoriesFor(category)
|
||||
.ToList()
|
||||
.ForEach(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all categories.
|
||||
/// </summary>
|
||||
/// <returns>All categories.</returns>
|
||||
public static IEnumerable<FlyTextCategory> GetAllCategories()
|
||||
=> Enum.GetValues<FlyTextCategory>()
|
||||
.Cast<FlyTextCategory>()
|
||||
.Where(category => category.IsCategory());
|
||||
|
||||
/// <summary>
|
||||
/// Gets all groups.
|
||||
/// </summary>
|
||||
/// <returns>All groups.</returns>
|
||||
public static IEnumerable<FlyTextCategory> GetAllGroups()
|
||||
=> Enum.GetValues<FlyTextCategory>()
|
||||
.Cast<FlyTextCategory>()
|
||||
.Where(group => group.IsGroup());
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// From FlyTextFilter - FlyTextCreation.cs
|
||||
// https://github.com/Aireil/FlyTextFilter
|
||||
|
||||
namespace CBT.Types;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/* Style:
|
||||
* 0 - is local player (or if source, is local player's pet)
|
||||
* 1 - is an alliance member
|
||||
* 2 - default if not the others
|
||||
* 3 - is a battle NPC, is in a duel with local player, or is a player character other than the local player in pvp instance or duel
|
||||
* If (source == 0 || target == 0) { Fly Text } else { Pop-up Text }
|
||||
*
|
||||
* (target == 0) is style 1, red on attacks.
|
||||
* (source == 0 && target == 3) is style 2, yellow on attacks.
|
||||
* (source == 3 && target != 0) is style 3, red on attacks.
|
||||
* ((source == 1 || source == 2) && target != 0) is style 4, blue on attacks.
|
||||
*
|
||||
*
|
||||
* Damage type:
|
||||
* 1 - Physical
|
||||
* 2 - Magic
|
||||
* 3 - Unique
|
||||
* */
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextCreation struct.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FlyTextCreation
|
||||
{
|
||||
/// <summary>
|
||||
/// Kind.
|
||||
/// </summary>
|
||||
public FlyTextKind FlyTextKind;
|
||||
|
||||
/// <summary>
|
||||
/// SourceStyle.
|
||||
/// </summary>
|
||||
public byte SourceStyle;
|
||||
|
||||
/// <summary>
|
||||
/// TargetStyle.
|
||||
/// </summary>
|
||||
public byte TargetStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Option.
|
||||
/// </summary>
|
||||
public byte Option;
|
||||
|
||||
/// <summary>
|
||||
/// ActionKind.
|
||||
/// </summary>
|
||||
public byte ActionKind;
|
||||
|
||||
/// <summary>
|
||||
/// ActionID.
|
||||
/// </summary>
|
||||
public int ActionId;
|
||||
|
||||
/// <summary>
|
||||
/// Value1.
|
||||
/// </summary>
|
||||
public int Val1; // status id if icon
|
||||
|
||||
/// <summary>
|
||||
/// Value2.
|
||||
/// </summary>
|
||||
public int Val2;
|
||||
|
||||
/// <summary>
|
||||
/// DamageType.
|
||||
/// </summary>
|
||||
public byte DamageType;
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
namespace CBT.Types;
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CBT.FlyText.Animations;
|
||||
using CBT.FlyText.Configuration;
|
||||
using CBT.Helpers;
|
||||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using static FFXIVClientStructs.FFXIV.Client.Game.Character.ActionEffectHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The fly text event.
|
||||
/// </summary>
|
||||
public unsafe partial class FlyTextEvent
|
||||
{
|
||||
private Vector2? flyTextSize;
|
||||
private string? formattedText;
|
||||
private Vector2? configuredOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind of the FlyText event.</param>
|
||||
/// <param name="effects">An array of effects associated with this event.</param>
|
||||
/// <param name="target">Target for the incoming FlyText event.</param>
|
||||
/// <param name="source">Source of the FlyText event.</param>
|
||||
/// <param name="option">Option. Unused.</param>
|
||||
/// <param name="actionKind">ActionKind of the FlyText event.</param>
|
||||
/// <param name="actionID">ActionID of the FlyText event.</param>
|
||||
/// <param name="val1">Val1 of the FlyText event.</param>
|
||||
/// <param name="val2">Val2 of the FlyText event.</param>
|
||||
/// <param name="val3">Val3 of the FlyText event.</param>
|
||||
/// <param name="val4">Val4 of the FlyText event.</param>
|
||||
public FlyTextEvent(FlyTextKind kind, Effect[] effects, Character* target, Character* source, int option, int actionKind, int actionID, int val1, int val2, int val3, int val4)
|
||||
{
|
||||
this.Kind = kind;
|
||||
this.Effects = effects;
|
||||
this.Target = target;
|
||||
this.Source = source;
|
||||
this.Option = option;
|
||||
this.ActionKind = actionKind;
|
||||
this.ActionID = actionID;
|
||||
this.Value1 = val1;
|
||||
this.Value2 = val2;
|
||||
this.Value3 = val3;
|
||||
this.Value4 = val4;
|
||||
|
||||
this.Config = new FlyTextConfiguration(kind);
|
||||
this.Animation = FlyTextAnimation.Create(kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextEvent"/> class.
|
||||
/// </summary>
|
||||
public FlyTextEvent()
|
||||
{
|
||||
this.Effects = null!;
|
||||
this.Target = null!;
|
||||
this.Source = null!;
|
||||
this.Option = default;
|
||||
this.ActionKind = default;
|
||||
this.ActionID = default;
|
||||
this.Value1 = default;
|
||||
this.Value2 = default;
|
||||
this.Value3 = default;
|
||||
this.Value4 = default;
|
||||
|
||||
this.Config = null!;
|
||||
this.Animation = null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// gets a value indicating whether an event is expired.
|
||||
/// </summary>
|
||||
public bool IsExpired
|
||||
=> this.Animation?.TimeElapsed > this.Animation?.Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating the world anchor of the event.
|
||||
/// </summary>
|
||||
public Vector2 Anchor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PluginManager.IsPlayerCharacter(this.Target))
|
||||
{
|
||||
Service.Configuration.Options.TryGetValue(GlobalOption.PlayerAnchor.ToString(), out var isAnchorFree);
|
||||
|
||||
if (isAnchorFree)
|
||||
{
|
||||
return Service.Configuration.FreeMoveAnchor;
|
||||
}
|
||||
}
|
||||
|
||||
return Service.GameGui.WorldToScreen(this.Target->Position, out Vector2 currentPosition) ? currentPosition : Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets offset from the configuration.
|
||||
/// </summary>
|
||||
public Vector2 Offset
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.configuredOffset == null)
|
||||
{
|
||||
if (Service.Configuration.FlyTextKinds.TryGetValue(this.Kind, out var kindConfig))
|
||||
{
|
||||
this.configuredOffset = kindConfig.Offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.configuredOffset = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
return (Vector2)this.configuredOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the FlyTextEvent with the animation offset applied.
|
||||
/// </summary>
|
||||
public Vector2 Position
|
||||
=> this.Anchor + this.Offset + this.Animation.Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the FlyTextEvent rectangle.
|
||||
/// </summary>
|
||||
public Vector2 Size
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.flyTextSize != null)
|
||||
{
|
||||
return (Vector2)this.flyTextSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Service.Fonts.Push(this.Config.Font.Name, this.Config.Font.Size))
|
||||
{
|
||||
var textSize = ImGui.CalcTextSize(this.Text);
|
||||
|
||||
if (!this.Config.Icon.Enabled)
|
||||
{
|
||||
return textSize;
|
||||
}
|
||||
|
||||
var iconSize = this.Config.Icon.Enabled ? this.Config.Icon.Size : Vector2.Zero;
|
||||
|
||||
var totalWidth = iconSize.X + textSize.X + this.Config.Icon.Offset.X;
|
||||
var totalHeight = Math.Max(textSize.Y, iconSize.Y);
|
||||
|
||||
this.flyTextSize = new Vector2(totalWidth, totalHeight);
|
||||
}
|
||||
|
||||
return (Vector2)this.flyTextSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of the FlyTextEvent Value1.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
this.formattedText = this.Kind.IsStatus() ? this.Format(this.Name.ExtractText()) : this.Kind.IsMessage() ? this.Format(this.Kind.Pretty()) : this.Format(this.Value1.ToString("N0"));
|
||||
|
||||
return this.formattedText;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud Texture Wrap for the ActionID.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap? Icon => Service.TextureProvider.GetFromGameIcon(new GameIconLookup(this.IconID)).GetWrapOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IconID for the provided Action.
|
||||
/// </summary>
|
||||
public uint IconID
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Kind.IsStatus())
|
||||
{
|
||||
return Service.Sheet.GetIconForStatus(this.Value1);
|
||||
}
|
||||
|
||||
if (this.Kind.IsOther())
|
||||
{
|
||||
return Service.Sheet.GetIconForItem(this.ActionID);
|
||||
}
|
||||
|
||||
return Service.Sheet.GetIconForAction(this.ActionID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Name for the provided Action.
|
||||
/// </summary>
|
||||
public ReadOnlySeString Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Kind.IsStatus())
|
||||
{
|
||||
return Service.Sheet.GetNameForStatus(this.Value1);
|
||||
}
|
||||
|
||||
if (this.Kind.IsOther())
|
||||
{
|
||||
return Service.Sheet.GetNameForItem(this.ActionID);
|
||||
}
|
||||
|
||||
return Service.Sheet.GetNameForAction(this.ActionID);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the damage type of an action.
|
||||
/// </summary>
|
||||
public DamageKind? DamageKind
|
||||
=> this.Kind.InCategory(FlyTextCategory.AbilityDamage)
|
||||
? (DamageKind)(this.Effects
|
||||
.ToList()
|
||||
.Where(effect => (ActionEventKind)effect.Type == ActionEventKind.Damage)
|
||||
.FirstOrDefault(default(Effect))
|
||||
.Param1 & 0xF)
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the positional state for an action.
|
||||
/// </summary>
|
||||
public PositionalState PositionalState
|
||||
=> this.Kind.InCategory(FlyTextCategory.AbilityDamage)
|
||||
? PositionalManager
|
||||
.PositionalSucceeded(this.ActionID, this.Effects.ToList().Where(effect => (ActionEventKind)effect.Type == ActionEventKind.Damage).FirstOrDefault().Param2)
|
||||
: PositionalState.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color for a flytext event.
|
||||
/// </summary>
|
||||
public Vector4 Color
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Config.Positionals)
|
||||
{
|
||||
return this.PositionalState switch
|
||||
{
|
||||
PositionalState.None => this.Config.Font.Color,
|
||||
PositionalState.Success => this.Config.Font.ColorSuccess,
|
||||
PositionalState.Failed => this.Config.Font.ColorFailed,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(this.PositionalState), this.PositionalState, null),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.Config.Font.Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the fly text kind.
|
||||
/// </summary>
|
||||
public FlyTextKind Kind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FlyTextConfiguration.
|
||||
/// </summary>
|
||||
public FlyTextConfiguration Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FlyTextAnimation.
|
||||
/// </summary>
|
||||
public FlyTextAnimation Animation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Effects associated with the event.
|
||||
/// </summary>
|
||||
public Effect[] Effects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Target from the original event.
|
||||
/// </summary>
|
||||
public Character* Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Source from the original event.
|
||||
/// </summary>
|
||||
public Character* Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Option from the original event.
|
||||
/// </summary>
|
||||
public int Option { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ActionKind from the original event.
|
||||
/// </summary>
|
||||
public int ActionKind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ActionID from the original event.
|
||||
/// </summary>
|
||||
public int ActionID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Value1 from the original event.
|
||||
/// </summary>
|
||||
public int Value1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Value2 from the original event.
|
||||
/// </summary>
|
||||
public int Value2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Value3 from the original event.
|
||||
/// </summary>
|
||||
public int Value3 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Value4 from the original event.
|
||||
/// </summary>
|
||||
public int Value4 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source ID from the original event.
|
||||
/// </summary>
|
||||
public uint SourceID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Update applies state changes to the FlyText event.
|
||||
/// </summary>
|
||||
/// <param name="timeElapsed">Time since the last frame.</param>
|
||||
public void Update(float timeElapsed)
|
||||
{
|
||||
if (this.Animation != null)
|
||||
{
|
||||
this.Animation.TimeElapsed += timeElapsed / 1000;
|
||||
this.Animation.Apply(this, timeElapsed / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the state.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
this.Kind = FlyTextKind.None;
|
||||
this.Effects = null!;
|
||||
this.Target = null!;
|
||||
this.Source = null!;
|
||||
this.Option = default;
|
||||
this.ActionKind = default;
|
||||
this.ActionID = default;
|
||||
this.Value1 = default;
|
||||
this.Value2 = default;
|
||||
this.Value3 = default;
|
||||
this.Value4 = default;
|
||||
|
||||
this.SourceID = default;
|
||||
|
||||
this.Config = null!;
|
||||
this.Animation = null!;
|
||||
|
||||
this.flyTextSize = default;
|
||||
this.formattedText = default;
|
||||
this.configuredOffset = default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Give life.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind of the FlyText event.</param>
|
||||
/// <param name="effects">An array of effects associated with this event.</param>
|
||||
/// <param name="sourceID">The source ID of the caster.</param>
|
||||
/// <param name="target">Target for the incoming FlyText event.</param>
|
||||
/// <param name="source">Source of the FlyText event.</param>
|
||||
/// <param name="option">Option. Unused.</param>
|
||||
/// <param name="actionKind">ActionKind of the FlyText event.</param>
|
||||
/// <param name="actionID">ActionID of the FlyText event.</param>
|
||||
/// <param name="val1">Val1 of the FlyText event.</param>
|
||||
/// <param name="val2">Val2 of the FlyText event.</param>
|
||||
/// <param name="val3">Val3 of the FlyText event.</param>
|
||||
/// <param name="val4">Val4 of the FlyText event.</param>
|
||||
public void Hydrate(FlyTextKind kind, Effect[] effects, uint sourceID, Character* target, Character* source, int option, int actionKind, int actionID, int val1, int val2, int val3, int val4)
|
||||
{
|
||||
this.Kind = kind;
|
||||
this.Effects = effects;
|
||||
this.Target = target;
|
||||
this.Source = source;
|
||||
this.Option = option;
|
||||
this.ActionKind = actionKind;
|
||||
this.ActionID = actionID;
|
||||
this.Value1 = val1;
|
||||
this.Value2 = val2;
|
||||
this.Value3 = val3;
|
||||
this.Value4 = val4;
|
||||
|
||||
// This is a special case for HP Regen attribution.
|
||||
this.SourceID = sourceID;
|
||||
|
||||
this.Config = new FlyTextConfiguration(kind);
|
||||
this.Animation = FlyTextAnimation.Create(kind);
|
||||
}
|
||||
|
||||
// TODO @cultbaus: I want to use string formatting and text tags instead of this, but for now this can be a placeholder.
|
||||
private string Format(string originalValue)
|
||||
{
|
||||
var outMessage = originalValue;
|
||||
|
||||
if (this.Config != null)
|
||||
{
|
||||
if (!this.Config.Message.Format)
|
||||
{
|
||||
return outMessage;
|
||||
}
|
||||
|
||||
if (this.Config.Message.Prefix != string.Empty)
|
||||
{
|
||||
outMessage = $"{this.Config.Message.Prefix} {outMessage}";
|
||||
}
|
||||
|
||||
if (this.Config.Message.Suffix != string.Empty)
|
||||
{
|
||||
outMessage = $"{outMessage} {this.Config.Message.Prefix}";
|
||||
}
|
||||
|
||||
return outMessage;
|
||||
}
|
||||
|
||||
return originalValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
namespace CBT.Types;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CBT.Attributes;
|
||||
using DalamudFlyText = Dalamud.Game.Gui.FlyText.FlyTextKind;
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextKind is the CBT wrapped enum of the Dalamud FlyTextKind enum.
|
||||
/// </summary>
|
||||
public enum FlyTextKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Auto attack or dot.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AutoAttack)]
|
||||
AutoAttackOrDot = DalamudFlyText.AutoAttackOrDot,
|
||||
|
||||
/// <summary>
|
||||
/// Auto attack or dot direct hit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AutoAttack)]
|
||||
AutoAttackOrDotDh = DalamudFlyText.AutoAttackOrDotDh,
|
||||
|
||||
/// <summary>
|
||||
/// Auto attack or dot crit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AutoAttack)]
|
||||
AutoAttackOrDotCrit = DalamudFlyText.AutoAttackOrDotCrit,
|
||||
|
||||
/// <summary>
|
||||
/// Auto attack or dot direct-hit-crit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AutoAttack)]
|
||||
AutoAttackOrDotCritDh = DalamudFlyText.AutoAttackOrDotCritDh,
|
||||
|
||||
/// <summary>
|
||||
/// Ability damage.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityDamage)]
|
||||
Damage = DalamudFlyText.Damage,
|
||||
|
||||
/// <summary>
|
||||
/// Ability damage direct hit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityDamage)]
|
||||
DamageDh = DalamudFlyText.DamageDh,
|
||||
|
||||
/// <summary>
|
||||
/// Ability damage crit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityDamage)]
|
||||
DamageCrit = DalamudFlyText.DamageCrit,
|
||||
|
||||
/// <summary>
|
||||
/// Ability damage direct-hit-crit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityDamage)]
|
||||
DamageCritDh = DalamudFlyText.DamageCritDh,
|
||||
|
||||
/// <summary>
|
||||
/// Miss, but sometimes dodge.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
Miss = DalamudFlyText.Miss,
|
||||
|
||||
/// <summary>
|
||||
/// Named miss.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
NamedMiss = DalamudFlyText.NamedMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Dodge.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
Dodge = DalamudFlyText.Dodge,
|
||||
|
||||
/// <summary>
|
||||
/// Dodge.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
NamedDodge = DalamudFlyText.NamedDodge,
|
||||
|
||||
/// <summary>
|
||||
/// Buff.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Buff)]
|
||||
Buff = DalamudFlyText.Buff,
|
||||
|
||||
/// <summary>
|
||||
/// Debuff.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Debuff)]
|
||||
Debuff = DalamudFlyText.Debuff,
|
||||
|
||||
/// <summary>
|
||||
/// Experience.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
Exp = DalamudFlyText.Exp,
|
||||
|
||||
/// <summary>
|
||||
/// Island Experience.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
IslandExp = DalamudFlyText.IslandExp,
|
||||
|
||||
/// <summary>
|
||||
/// MpDrain.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
MpDrain = DalamudFlyText.MpDrain,
|
||||
|
||||
/// <summary>
|
||||
/// Healing.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityHealing)]
|
||||
Healing = DalamudFlyText.Healing,
|
||||
|
||||
/// <summary>
|
||||
/// MpRegen ticks.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityHealing)]
|
||||
MpRegen = DalamudFlyText.MpRegen,
|
||||
|
||||
/// <summary>
|
||||
/// EpRegen.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy, FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
EpRegen = DalamudFlyText.EpRegen,
|
||||
|
||||
/// <summary>
|
||||
/// CpRegen.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy, FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
CpRegen = DalamudFlyText.CpRegen,
|
||||
|
||||
/// <summary>
|
||||
/// GpRegen.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy, FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
GpRegen = DalamudFlyText.GpRegen,
|
||||
|
||||
/// <summary>
|
||||
/// None.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy, FlyTextFilter.Self])]
|
||||
[FlyTextCategory(FlyTextCategory.CategoryNone)]
|
||||
None = DalamudFlyText.None,
|
||||
|
||||
/// <summary>
|
||||
/// Invulnerable.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Self])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
Invulnerable = DalamudFlyText.Invulnerable,
|
||||
|
||||
/// <summary>
|
||||
/// Interrupted.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
Interrupted = DalamudFlyText.Interrupted,
|
||||
|
||||
/// <summary>
|
||||
/// Crafting.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
CraftingProgress = DalamudFlyText.CraftingProgress,
|
||||
|
||||
/// <summary>
|
||||
/// Crafting Quality.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
CraftingQuality = DalamudFlyText.CraftingQuality,
|
||||
|
||||
/// <summary>
|
||||
/// Crafting Quality Crit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
CraftingQualityCrit = DalamudFlyText.CraftingQualityCrit,
|
||||
|
||||
/// <summary>
|
||||
/// Healing crit.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityHealing)]
|
||||
HealingCrit = DalamudFlyText.HealingCrit,
|
||||
|
||||
/// <summary>
|
||||
/// Debuff "has no effect".
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
DebuffNoEffect = DalamudFlyText.DebuffNoEffect,
|
||||
|
||||
/// <summary>
|
||||
/// Buff with faded text.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Buff)]
|
||||
BuffFading = DalamudFlyText.BuffFading,
|
||||
|
||||
/// <summary>
|
||||
/// Debuff with faded text.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Debuff)]
|
||||
DebuffFading = DalamudFlyText.DebuffFading,
|
||||
|
||||
/// <summary>
|
||||
/// Named. Unknown.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Debuff)]
|
||||
Named = DalamudFlyText.Named,
|
||||
|
||||
/// <summary>
|
||||
/// Debuff was resisted.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
DebuffResisted = DalamudFlyText.DebuffResisted,
|
||||
|
||||
/// <summary>
|
||||
/// Target is incapacitated.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.None])]
|
||||
[FlyTextCategory(FlyTextCategory.CC)]
|
||||
Incapacitated = DalamudFlyText.Incapacitated,
|
||||
|
||||
/// <summary>
|
||||
/// Ability is fully resisted.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
FullyResisted = DalamudFlyText.FullyResisted,
|
||||
|
||||
/// <summary>
|
||||
/// Ability has no effect.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
HasNoEffect = DalamudFlyText.HasNoEffect,
|
||||
|
||||
/// <summary>
|
||||
/// HP Drain, bloodbath.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.AbilityHealing)]
|
||||
HpDrain = DalamudFlyText.HpDrain,
|
||||
|
||||
/// <summary>
|
||||
/// Debuff immued due to invuln.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
DebuffInvulnerable = DalamudFlyText.DebuffInvulnerable,
|
||||
|
||||
/// <summary>
|
||||
/// Resisted.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
Resist = DalamudFlyText.Resist,
|
||||
|
||||
/// <summary>
|
||||
/// Looted Item.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
LootedItem = DalamudFlyText.LootedItem,
|
||||
|
||||
/// <summary>
|
||||
/// Collectible.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
Collectability = DalamudFlyText.Collectability,
|
||||
|
||||
/// <summary>
|
||||
/// Crit collectible.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
CollectabilityCrit = DalamudFlyText.CollectabilityCrit,
|
||||
|
||||
/// <summary>
|
||||
/// Reflect.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
Reflect = DalamudFlyText.Reflect,
|
||||
|
||||
/// <summary>
|
||||
/// Reflected.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party])]
|
||||
[FlyTextCategory(FlyTextCategory.Miss)]
|
||||
Reflected = DalamudFlyText.Reflected,
|
||||
|
||||
/// <summary>
|
||||
/// Crafting quality DH.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
CraftingQualityDh = DalamudFlyText.CraftingQualityDh,
|
||||
|
||||
/// <summary>
|
||||
/// Crafting Quality crit DH.
|
||||
/// </summary>
|
||||
[FlyTextFilter([FlyTextFilter.Party, FlyTextFilter.Enemy])]
|
||||
[FlyTextCategory(FlyTextCategory.Other)]
|
||||
CraftingQualityCritDh = DalamudFlyText.CraftingQualityCritDh,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FlyTextKinds implements methods on the FlyTextKind enum.
|
||||
/// </summary>
|
||||
public static class FlyTextKindExtension
|
||||
{
|
||||
private static readonly Dictionary<FlyTextKind, FlyTextCategory> CategoryCache = new Dictionary<FlyTextKind, FlyTextCategory>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the category for a given FlyTextKind.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind enum.</param>
|
||||
/// <returns>Category for which the kind is a member.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws if the kind has no category.</exception>
|
||||
public static FlyTextCategory GetCategory(this FlyTextKind kind)
|
||||
{
|
||||
if (CategoryCache.TryGetValue(kind, out var category))
|
||||
{
|
||||
return category;
|
||||
}
|
||||
|
||||
var attr = typeof(FlyTextKind)
|
||||
.GetMember(kind.ToString())[0]
|
||||
.GetCustomAttributes(typeof(FlyTextCategoryAttribute), false);
|
||||
|
||||
if (attr.Length > 0)
|
||||
{
|
||||
category = ((FlyTextCategoryAttribute)attr[0]).Category;
|
||||
CategoryCache[kind] = category;
|
||||
return category;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the constant filter for an event.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKidn enum.</param>
|
||||
/// <returns>The filter for the kind.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws if the kind has no filter.</exception>
|
||||
public static FlyTextFilter[] GetFilter(this FlyTextKind kind)
|
||||
{
|
||||
var attr = typeof(FlyTextKind)
|
||||
.GetMember(kind.ToString())[0]
|
||||
.GetCustomAttributes(typeof(FlyTextFilterAttribute), false);
|
||||
|
||||
return attr.Length > 0
|
||||
? ((FlyTextFilterAttribute)attr[0]).Filter
|
||||
: throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the event should filter against the type.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind to filter.</param>
|
||||
/// <param name="filter">Filter to compare.</param>
|
||||
/// <returns>Bool.</returns>
|
||||
public static bool ShouldFilter(this FlyTextKind kind, FlyTextFilter filter)
|
||||
=> kind.GetFilter().Length > 0 && kind.GetFilter().Contains(filter);
|
||||
|
||||
/// <summary>
|
||||
/// Opposite of ShouldFilter.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind to allow.</param>
|
||||
/// <param name="filter">Filter to compare.</param>
|
||||
/// <returns>Bool.</returns>
|
||||
public static bool ShouldAllow(this FlyTextKind kind, FlyTextFilter filter)
|
||||
=> !kind.ShouldFilter(filter);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a kind is in a category.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind to look up.</param>
|
||||
/// <param name="category">FlyTextCategory to check if kind is a member of.</param>
|
||||
/// <returns>True if the kind is a member of that category.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws if the category is a group.</exception>
|
||||
public static bool InCategory(this FlyTextKind kind, FlyTextCategory category)
|
||||
=> category.IsCategory()
|
||||
? GetCategory(kind) == category
|
||||
: throw new ArgumentOutOfRangeException(nameof(category), category, null);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a kind is in a group.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind to look up.</param>
|
||||
/// <param name="group">FlyTextCategory to check if kind is a member of.</param>
|
||||
/// <returns>True if the kind is a member of that group.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws if the group is a category.</exception>
|
||||
public static bool InGroup(this FlyTextKind kind, FlyTextCategory group)
|
||||
=> group.IsGroup()
|
||||
? GetCategory(kind).HasFlag(group)
|
||||
: throw new ArgumentOutOfRangeException(nameof(group), group, null);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a kind is a status.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind.</param>
|
||||
/// <returns>Bool indicating if the kind is a status effect.</returns>
|
||||
public static bool IsStatus(this FlyTextKind kind)
|
||||
=> kind.InCategory(FlyTextCategory.Buff) || kind.InCategory(FlyTextCategory.Debuff);
|
||||
|
||||
/// <summary>
|
||||
/// Check if this kind is other.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind.</param>
|
||||
/// <returns>Bool if this is an "other" kind.</returns>
|
||||
public static bool IsOther(this FlyTextKind kind)
|
||||
=> kind.InCategory(FlyTextCategory.Other);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a kind is a message-only.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind.</param>
|
||||
/// <returns>Bool indicating if the kind is a message-only kind.</returns>
|
||||
public static bool IsMessage(this FlyTextKind kind)
|
||||
=> kind.InCategory(FlyTextCategory.Miss) || kind.InCategory(FlyTextCategory.CC);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a kind is a spell.
|
||||
/// </summary>
|
||||
/// <param name="kind">FlyTextKind.</param>
|
||||
/// <returns>Bool indicating if the kind is a spell/ability.</returns>
|
||||
public static bool IsSpell(this FlyTextKind kind)
|
||||
=> !IsStatus(kind) && !IsOther(kind);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all kinds.
|
||||
/// </summary>
|
||||
/// <returns>All kinds.</returns>
|
||||
public static IEnumerable<FlyTextKind> GetAll()
|
||||
=> Enum.GetValues<FlyTextKind>()
|
||||
.Cast<FlyTextKind>();
|
||||
|
||||
/// <summary>
|
||||
/// Pretty print a value.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind to print.</param>
|
||||
/// <returns>A pretty string.</returns>
|
||||
public static string Pretty(this FlyTextKind kind)
|
||||
=> kind switch
|
||||
{
|
||||
FlyTextKind.Miss => "Miss",
|
||||
FlyTextKind.NamedMiss => "Miss",
|
||||
FlyTextKind.Dodge => "Dodge",
|
||||
FlyTextKind.NamedDodge => "Dodge",
|
||||
FlyTextKind.DebuffNoEffect => "No Effect",
|
||||
FlyTextKind.DebuffResisted => "Resisted",
|
||||
FlyTextKind.Incapacitated => "Incapacitated",
|
||||
FlyTextKind.FullyResisted => "Resisted",
|
||||
FlyTextKind.HasNoEffect => "No Effect",
|
||||
FlyTextKind.DebuffInvulnerable => "Invulnerable",
|
||||
FlyTextKind.Invulnerable => "Invulnerable",
|
||||
FlyTextKind.Resist => "Resisted",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Predicate for filtering unused kinds.
|
||||
/// </summary>
|
||||
/// <param name="kind">The kind to compare.</param>
|
||||
/// <returns>A bool if the value should filter.</returns>
|
||||
public static bool UnusedKindPredicate(FlyTextKind kind)
|
||||
=> kind != FlyTextKind.None;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"requested": "[14.0.1, )",
|
||||
"resolved": "14.0.1",
|
||||
"contentHash": "y0WWyUE6dhpGdolK3iKgwys05/nZaVf4ZPtIjpLhJBZvHxkkiE23zYRo7K7uqAgoK/QvK5cqF6l3VG5AbgC6KA=="
|
||||
},
|
||||
"DotNet.ReproducibleBuilds": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.39, )",
|
||||
"resolved": "1.2.39",
|
||||
"contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.0-beta.556, )",
|
||||
"resolved": "1.2.0-beta.556",
|
||||
"contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers.Unstable": "1.2.0.556"
|
||||
}
|
||||
},
|
||||
"StyleCop.Analyzers.Unstable": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.2.0.556",
|
||||
"contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 cultbaus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,46 @@
|
||||
# CBT – Local build (Dalamud API 14)
|
||||
|
||||
This copy of CBT has been updated to **Dalamud API level 14** so it runs on current FFXIV/Dalamud. The upstream repo may still ship API 13, which shows as "outdated and incompatible."
|
||||
|
||||
## Install the built plugin
|
||||
|
||||
### Option A: Dev plugins folder (recommended)
|
||||
|
||||
1. Create the folder (if it doesn’t exist):
|
||||
- `%AppData%\XIVLauncher\addon\Hooks\dev\plugins\CBT\`
|
||||
2. Copy everything from:
|
||||
- `CBT\bin\Release\`
|
||||
into that folder, so the DLL and manifest are directly inside `...\plugins\CBT\`:
|
||||
- `CBT.dll`
|
||||
- `CBT.json`
|
||||
- `CBT.deps.json`
|
||||
- `Media\` (folder)
|
||||
- `Data\` (folder)
|
||||
- `images\` (folder)
|
||||
3. In-game: enable **test/development** plugins in Dalamud settings if required, then enable **CBT**.
|
||||
|
||||
### Option B: Install from zip
|
||||
|
||||
A zip is built at:
|
||||
|
||||
- `C:\Users\flick\Desktop\MyDalamudPlugins\release-zips\CBT.zip`
|
||||
|
||||
If your Dalamud/plugin installer supports “Install from custom zip” or “Install from file”, use that and select `CBT.zip`. Otherwise use Option A.
|
||||
|
||||
## Rebuild after changes
|
||||
|
||||
From repo root:
|
||||
|
||||
```powershell
|
||||
cd "C:\Users\flick\Desktop\MyDalamudPlugins\repos\CBT-main"
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
Output: `CBT\bin\Release\`. Copy those contents into `...\dev\plugins\CBT\` again (or re-zip and reinstall).
|
||||
|
||||
## Changes made for API 14
|
||||
|
||||
- **CBT.csproj**: `Dalamud.NET.Sdk` 13.0.0 → 14.0.1, `net9.0-windows` → `net10.0-windows`, version set to 0.0.3.7.
|
||||
- **CBT.json**: `DalamudApiLevel` 13 → 14, added `InternalName`, `ApplicableVersion`.
|
||||
- **PluginAddressResolver.cs**: Added `using Dalamud.Plugin.Services` so `ISigScanner` resolves (moved in API 14).
|
||||
- **PluginManager.cs**: `LocalPlayer` now uses `Service.ObjectTable.LocalPlayer` instead of obsolete `IClientState.LocalPlayer`.
|
||||
@@ -0,0 +1,39 @@
|
||||
<div align="center">
|
||||
|
||||
<img height="200px" src="./res/icon.png" />
|
||||
|
||||
## Cultbaus Battle Text
|
||||
|
||||
<a href="https://discord.gg/xPxfMSWwjq">  </a>
|
||||
|
||||
[Quick Start](#quick-start) • [Prior Art](#prior-art) • [Attribution](#attribution) • [Help Wanted](#help-wanted)
|
||||
|
||||
<img height="400px" src="./res/record.gif" />
|
||||
|
||||
An ImGui replacement for FlyText in Final Fantasy XIV.
|
||||
|
||||
</div>
|
||||
|
||||
# Quick Start
|
||||
|
||||
I'll preface this section saying that, at the time of writing, this is still very much in development, and all releases should be considered unstable. There *will* be breaking changes that will not have migration support. You have been warned!
|
||||
|
||||
Add the repository to your custom repos list in Dalamud -> Settings -> Experimental: `https://raw.githubusercontent.com/cultbaus/MyDalamudPlugins/main/repo.json`, then install the plugin. Use `/cbt` to configure.
|
||||
|
||||
# Prior Art
|
||||
|
||||
Thank you to the creators & maintainers of [FlyTextFilter][1] and [DamageInfo][2] for providing inspiration and an in-code reference implementation for where to begin. It would literally not have been possible for me to begin work on this without their efforts.
|
||||
|
||||
# Attribution
|
||||
|
||||
Additionally, thank you to the creators & maintainers of [DelvUI][4] and [XIVComboExpanded][3] for providing additional reference material and implementation details that were lost on me from the official documentation sources. Similar to the aforementioned prior art, these plugins were instrumental in providing me a basis for which I could begin.
|
||||
|
||||
# Help Wanted
|
||||
|
||||
* Localization support
|
||||
* [FontManager](./CBT/Helpers/FontManager.cs) could use improvement - perhaps using the Dalamud global picker to load all the fonts available to a user.
|
||||
|
||||
[1]: https://github.com/Aireil/FlyTextFilter
|
||||
[2]: https://github.com/lmcintyre/DamageInfoPlugin
|
||||
[3]: https://github.com/MKhayle/XIVComboExpanded
|
||||
[4]: https://github.com/DelvUI/DelvUI
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.9 MiB |
Reference in New Issue
Block a user