Compare commits
6 Commits
a52b9e8d76
..
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 960114c142 | |||
| 356da24feb | |||
| c7917afd67 | |||
| 862fb4782c | |||
| 614e97abf7 | |||
| b598c03e9e |
+23
-1
@@ -1,3 +1,25 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
# Asriel's upstream default: CRLF for text files (Windows-only dev workflow).
|
||||
# Kept intact to minimise the diff against upstream Craftimizer.
|
||||
* text eol=crlf
|
||||
|
||||
# Hellion Forge override: shell scripts and git hooks MUST be LF only,
|
||||
# otherwise bash on Linux refuses to execute them with
|
||||
# "env: »bash\r«: Datei nicht gefunden". This blocked the v0.1.0 release
|
||||
# pipeline push on Linux before .gitattributes was extended.
|
||||
*.sh text eol=lf
|
||||
.githooks/* text eol=lf
|
||||
scripts/* text eol=lf
|
||||
|
||||
# csharpier check fails with "different line endings than formatting would
|
||||
# result in" when .cs files are CRLF. The Gitea Actions runners are Linux
|
||||
# and the local dev box is Linux too, so force LF for the entire source
|
||||
# tree and the supporting text manifests.
|
||||
*.cs text eol=lf
|
||||
*.csproj text eol=lf
|
||||
*.sln text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
|
||||
*.png binary
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
|
||||
### Install
|
||||
|
||||
Add the Hellion Forge custom repository in Dalamud Settings
|
||||
(`/xlsettings` → **Experimental** → **Custom Plugin Repositories**):
|
||||
|
||||
```text
|
||||
https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/raw/branch/main/repo.json
|
||||
```
|
||||
|
||||
Save, then go to `/xlplugins` → **All Plugins** → Refresh. Forgeimizer
|
||||
shows up in the list — install it like any other plugin.
|
||||
|
||||
> ⚠️ **Conflict notice:** Forgeimizer refuses to load if the upstream
|
||||
> Craftimizer plugin is active. Disable upstream Craftimizer in `/xlplugins`
|
||||
> before enabling Forgeimizer.
|
||||
|
||||
### Licence and Attribution
|
||||
|
||||
MIT (same licence as upstream Craftimizer). Crafting logic, solver,
|
||||
simulator, recipe data, synthesis hooks, and UI windows by
|
||||
[Asriel Camora](https://github.com/WorkingRobot) — see
|
||||
[NOTICE.md](https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/src/branch/main/NOTICE.md)
|
||||
for the full acknowledgement and contact path.
|
||||
|
||||
Hellion Forge fork-maintenance (rebrand + SDK 15 migration + conflict
|
||||
detector) by Jon Kazama / Hellion Online Media.
|
||||
|
||||
### Documentation
|
||||
|
||||
- [README.md](https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/src/branch/main/README.md)
|
||||
- [CHANGELOG.md](https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/src/branch/main/CHANGELOG.md)
|
||||
- [NOTICE.md](https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/src/branch/main/NOTICE.md)
|
||||
- [COPYRIGHT](https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/src/branch/main/COPYRIGHT)
|
||||
|
||||
Maintained under **Hellion Forge** | [hellion-media.de](https://hellion-media.de)
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Build
|
||||
|
||||
# Verifies that every push to main and every PR still builds against the
|
||||
# current Dalamud staging branch. Does not produce release artefacts; the
|
||||
# release workflow handles that on tag.
|
||||
#
|
||||
# Linux runner: gitea.com Cloud Actions provides ubuntu-latest. The plugin
|
||||
# csproj targets net10.0-windows, but `dotnet build` cross-compiles on Linux
|
||||
# as long as the Dalamud staging assemblies are present at the expected
|
||||
# lookup path ($(HOME)/.xlcore/dalamud/Hooks/dev/, which Dalamud SDK 15 uses
|
||||
# on Linux).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# Minimum permissions for a build-only workflow: read the repo, nothing else.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (Release)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Download Dalamud staging
|
||||
run: |
|
||||
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||
mkdir -p "$hooks"
|
||||
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||
unzip -oq dalamud.zip -d "$hooks"
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore Craftimizer/Craftimizer.csproj -p:Platform=x64
|
||||
|
||||
- name: Build (Release)
|
||||
# -p:Platform=x64 is required: csproj declares <Platforms>x64</Platforms>
|
||||
# and <RuntimeIdentifier>win-x64</RuntimeIdentifier>, but project-level
|
||||
# dotnet build defaults to AnyCPU and emits to bin/Release/ instead of
|
||||
# bin/x64/Release/. The release workflow's find step expects the latter.
|
||||
run: dotnet build Craftimizer/Craftimizer.csproj --configuration Release --no-restore -p:Platform=x64
|
||||
@@ -0,0 +1,143 @@
|
||||
name: Release
|
||||
|
||||
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
||||
# current Dalamud staging branch, locates the latest.zip produced by
|
||||
# DalamudPackager, extracts the matching changelog block from CHANGELOG.md,
|
||||
# and attaches everything to the Gitea Release.
|
||||
#
|
||||
# User-controlled inputs touched by this workflow:
|
||||
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
||||
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
||||
# All other values are either repo-controlled (paths under
|
||||
# Craftimizer/bin/x64/Release derived from find) or pinned URLs to goatcorp
|
||||
# / gitea. Nothing from a webhook event payload (issue/PR titles, commit
|
||||
# messages, etc.) flows into a run-step.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
# Manual recovery trigger. Use Gitea's "Run workflow" UI and select the
|
||||
# tag (e.g. v0.1.0) from the Ref dropdown - not main. The Validate tag
|
||||
# ref step below hard-fails if a non-tag ref is selected, because
|
||||
# release-action reads GITHUB_REF directly and rejects anything that
|
||||
# does not start with refs/tags/.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build and attach release ZIP
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Validate tag ref
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
|
||||
echo "::error::Release workflow must run on a v*.X.Y tag ref, got ${GITHUB_REF}"
|
||||
echo "::error::Push a tag, or pick the tag (not main) in the workflow_dispatch Ref dropdown."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Download Dalamud staging
|
||||
run: |
|
||||
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||
mkdir -p "$hooks"
|
||||
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||
unzip -oq dalamud.zip -d "$hooks"
|
||||
|
||||
- name: Build (Release)
|
||||
# -p:Platform=x64 is required: csproj declares <Platforms>x64</Platforms>
|
||||
# and <RuntimeIdentifier>win-x64</RuntimeIdentifier>, but project-level
|
||||
# dotnet build defaults to AnyCPU and emits to bin/Release/ instead of
|
||||
# bin/x64/Release/. The Locate latest.zip step below expects the latter.
|
||||
run: dotnet build Craftimizer/Craftimizer.csproj --configuration Release -p:Platform=x64
|
||||
|
||||
- name: Locate latest.zip
|
||||
id: locate
|
||||
run: |
|
||||
zip="$(find Craftimizer/bin/x64/Release -name latest.zip -print -quit)"
|
||||
if [ -z "$zip" ]; then
|
||||
echo "latest.zip not found under Craftimizer/bin/x64/Release" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Found: $zip"
|
||||
echo "path=$zip" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Extract the changelog block for the tagged version from CHANGELOG.md.
|
||||
# Convention: each release block starts with `## vX.Y.Z` and ends at
|
||||
# the next `## v` or the `---` trailer at the bottom. Fails the
|
||||
# workflow if no block exists for the tagged version, which is the
|
||||
# automated counterpart to the "csproj + repo.json + CHANGELOG kept
|
||||
# in sync" rule.
|
||||
- name: Generate release body
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="$TAG_NAME"
|
||||
if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Refusing to generate release body for non-semver tag: $tag"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${tag#v}"
|
||||
changelog="CHANGELOG.md"
|
||||
|
||||
# awk extracts the block starting at "## vX.Y.Z" and stops at the
|
||||
# next "## v" or the "---" trailer.
|
||||
block="$(awk -v ver="## v${version}" '
|
||||
$0 ~ "^"ver" " { take=1; print; next }
|
||||
take && /^## v[0-9]+\./ { exit }
|
||||
take && /^---$/ { exit }
|
||||
take { print }
|
||||
' "$changelog")"
|
||||
|
||||
if [ -z "$block" ]; then
|
||||
echo "::error::No changelog entry for version $version found in $changelog. Update the changelog before tagging a release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
footer=""
|
||||
if [ -f .gitea/release-footer.md ]; then
|
||||
footer="$(cat .gitea/release-footer.md)"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "$block"
|
||||
echo ""
|
||||
echo "$footer"
|
||||
} > release-body.md
|
||||
|
||||
echo "Generated release body for $tag:"
|
||||
echo "----------------------------------------"
|
||||
cat release-body.md
|
||||
echo "----------------------------------------"
|
||||
|
||||
- name: Expose release body for release-action
|
||||
id: body
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo 'content<<RELEASE_BODY_EOF'
|
||||
cat release-body.md
|
||||
echo 'RELEASE_BODY_EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Attach to Gitea release
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: ${{ steps.locate.outputs.path }}
|
||||
body: ${{ steps.body.outputs.content }}
|
||||
api_key: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Security
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
uses: JonKazama-Hellion/security-workflows/.gitea/workflows/security-scan.yml@main
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
# .githooks/pre-push — invokes preflight.sh (A=version, B=build, C=csharpier, D=markdownlint).
|
||||
exec scripts/preflight.sh
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"MD003": { "style": "atx" },
|
||||
"MD004": { "style": "dash" },
|
||||
"MD007": { "indent": 2 },
|
||||
"MD009": { "br_spaces": 2, "strict": false, "list_item_empty_lines": false },
|
||||
"MD013": false,
|
||||
"MD024": { "siblings_only": true },
|
||||
"MD029": false,
|
||||
"MD033": false,
|
||||
"MD036": false,
|
||||
"MD040": true,
|
||||
"MD041": false,
|
||||
"MD046": { "style": "fenced" },
|
||||
"MD048": { "style": "backtick" },
|
||||
"MD049": { "style": "underscore" },
|
||||
"MD050": { "style": "asterisk" },
|
||||
"MD060": false
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Changelog
|
||||
|
||||
## v0.1.0 — First Hellion fork release (2026-05-26)
|
||||
|
||||
Initial Hellion Forge maintenance release of the Craftimizer fork. Combines
|
||||
the Dalamud SDK 15 migration, the rebrand to Forgeimizer, and the conflict
|
||||
detector into one shippable plugin.
|
||||
|
||||
- **Dalamud SDK 14 → 15** for FFXIV 7.5+ compatibility. Migrates the call
|
||||
sites the SDK 15 compiler refused: `FFXIVClientStructs.FFXIV.Component.GUI.ValueType`
|
||||
renamed to `AtkValueType`; Dalamud `ImRaii.IEndObject` (removed in SDK 15)
|
||||
replaced by a local `IEndObject` interface in `ImRaii2.cs`; direct Dalamud
|
||||
ImRaii returns switched to typed disposables (`ImRaii.TabItemDisposable`,
|
||||
`ImRaii.ColorDisposable`); `Settings.TabItem` ref-bool lifetime issue fixed
|
||||
by switching to the `ImRaii.TabItem(label, flags)` overload.
|
||||
- **Rebrand to Forgeimizer.** Assembly name, plugin manifest, `WindowSystem`
|
||||
name, the About-tab header, the `MacroMate` default, and a new
|
||||
`/forgeimizer` slash command alias all use the new name. The plugin
|
||||
installs into its own `pluginConfigs/Forgeimizer/` slot and shows up
|
||||
separately in `/xlplugins`.
|
||||
- **Conflict detector.** Forgeimizer refuses to load if upstream Craftimizer
|
||||
is active in the same Dalamud instance. Throws an `InvalidOperationException`
|
||||
with a clear message pointing at `/xlplugins`. Both plugins hook
|
||||
`UseAction` and `IsActionHighlighted`, so running them in parallel would
|
||||
corrupt either's state.
|
||||
- **Hellion Forge custom repo.** Friend-circle distribution via
|
||||
`https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/raw/branch/main/repo.json`
|
||||
for Dalamud's custom plugin repositories list.
|
||||
|
||||
Internal namespaces (`Craftimizer.*`) intentionally left alone. Crafting
|
||||
logic, solver, simulator, recipe data layer, synthesis hooks, macro engine,
|
||||
and all UI windows are unchanged from upstream Craftimizer 2.9.1.1.
|
||||
|
||||
Based on Craftimizer 2.9.1.1 (upstream WorkingRobot/Craftimizer, MIT).
|
||||
|
||||
---
|
||||
|
||||
Full history:
|
||||
<https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/releases>
|
||||
@@ -1,10 +1,10 @@
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Solver;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Solver;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
@@ -13,7 +13,8 @@ public class StoredActionTypeConverter : JsonConverter<ActionType[]>
|
||||
public override ActionType[] Read(
|
||||
ref Utf8JsonReader reader,
|
||||
Type typeToConvert,
|
||||
JsonSerializerOptions options)
|
||||
JsonSerializerOptions options
|
||||
)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartArray)
|
||||
throw new JsonException();
|
||||
@@ -87,7 +88,8 @@ public class StoredActionTypeConverter : JsonConverter<ActionType[]>
|
||||
public override void Write(
|
||||
Utf8JsonWriter writer,
|
||||
ActionType[] value,
|
||||
JsonSerializerOptions options)
|
||||
JsonSerializerOptions options
|
||||
)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in value)
|
||||
@@ -101,14 +103,18 @@ public class Macro
|
||||
public static event Action<Macro>? OnMacroChanged;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
[JsonInclude] [JsonPropertyName("Actions")]
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("Actions")]
|
||||
internal ActionType[] actions { get; set; } = [];
|
||||
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<ActionType> Actions
|
||||
{
|
||||
get => actions;
|
||||
set => ActionEnumerable = value;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<ActionType> ActionEnumerable
|
||||
{
|
||||
@@ -127,7 +133,7 @@ public class MacroCopyConfiguration
|
||||
OpenWindow, // useful for big macros
|
||||
CopyToMacro, // (add option for down or right) (max macro count; open copy-paste window if too much)
|
||||
CopyToClipboard,
|
||||
CopyToMacroMate
|
||||
CopyToMacroMate,
|
||||
}
|
||||
|
||||
public CopyType Type { get; set; } = CopyType.OpenWindow;
|
||||
@@ -172,13 +178,15 @@ public partial class Configuration
|
||||
{
|
||||
Colorful,
|
||||
Simple,
|
||||
None
|
||||
None,
|
||||
}
|
||||
|
||||
public static event Action? OnMacroListChanged;
|
||||
|
||||
[JsonInclude] [JsonPropertyName("Macros")]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("Macros")]
|
||||
internal List<Macro> macros { get; private set; } = [];
|
||||
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<Macro> Macros => macros;
|
||||
public int ReliabilitySimulationCount { get; set; } = 1000;
|
||||
@@ -244,10 +252,8 @@ public partial class Configuration
|
||||
[JsonSerializable(typeof(Configuration))]
|
||||
internal sealed partial class JsonContext : JsonSerializerContext
|
||||
{
|
||||
public static JsonSerializerOptions DeserializeOptions { get; } = new()
|
||||
{
|
||||
Converters = { new StoredActionTypeConverter() }
|
||||
};
|
||||
public static JsonSerializerOptions DeserializeOptions { get; } =
|
||||
new() { Converters = { new StoredActionTypeConverter() } };
|
||||
}
|
||||
|
||||
public void Save()
|
||||
@@ -265,7 +271,8 @@ public partial class Configuration
|
||||
using var stream = f.OpenRead();
|
||||
|
||||
// System.InvalidOperationException: Setting init-only properties is not supported in source generation mode.
|
||||
return JsonSerializer.Deserialize<Configuration>(stream, JsonContext.DeserializeOptions) ?? new();
|
||||
return JsonSerializer.Deserialize<Configuration>(stream, JsonContext.DeserializeOptions)
|
||||
?? new();
|
||||
}
|
||||
return new();
|
||||
}
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
||||
<PropertyGroup>
|
||||
<Authors>Asriel Camora (original); Jon Kazama / Hellion Forge (fork)</Authors>
|
||||
<Version>2.9.1.1</Version>
|
||||
<AssemblyName>Forgeimizer</AssemblyName>
|
||||
<PackageProjectUrl>https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer</PackageProjectUrl>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Authors>Asriel Camora (original); Jon Kazama / Hellion Forge (fork)</Authors>
|
||||
<Version>0.1.0</Version>
|
||||
<AssemblyName>Forgeimizer</AssemblyName>
|
||||
<PackageProjectUrl>https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer</PackageProjectUrl>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Graphics\icon.png" />
|
||||
<EmbeddedResource Include="Graphics\horse_icon.png" />
|
||||
<EmbeddedResource Include="Graphics\collectible_badge.png" />
|
||||
<EmbeddedResource Include="Graphics\expert.png" />
|
||||
<EmbeddedResource Include="Graphics\expert_badge.png" />
|
||||
<EmbeddedResource Include="Graphics\no_manip.png" />
|
||||
<EmbeddedResource Include="Graphics\specialist.png" />
|
||||
<EmbeddedResource Include="Graphics\splendorous.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.264">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\Simulator\Craftimizer.Simulator.csproj" />
|
||||
<ProjectReference Include="..\Solver\Craftimizer.Solver.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Graphics\icon.png" />
|
||||
<EmbeddedResource Include="Graphics\horse_icon.png" />
|
||||
<EmbeddedResource Include="Graphics\collectible_badge.png" />
|
||||
<EmbeddedResource Include="Graphics\expert.png" />
|
||||
<EmbeddedResource Include="Graphics\expert_badge.png" />
|
||||
<EmbeddedResource Include="Graphics\no_manip.png" />
|
||||
<EmbeddedResource Include="Graphics\specialist.png" />
|
||||
<EmbeddedResource Include="Graphics\splendorous.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.264">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\Simulator\Craftimizer.Simulator.csproj" />
|
||||
<ProjectReference Include="..\Solver\Craftimizer.Solver.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
+86
-20
@@ -1,9 +1,9 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
@@ -11,22 +11,50 @@ internal static unsafe class ImGuiExtras
|
||||
{
|
||||
// https://github.com/ImGuiNET/ImGui.NET/blob/069363672fed940ebdaa02f9b032c282b66467c7/src/CodeGenerator/definitions/cimgui/definitions.json#L25394
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern unsafe byte igInputTextEx(byte* label, byte* hint, byte* buf, int buf_size, Vector2 size, ImGuiInputTextFlags flags, ImGuiInputTextCallback? callback, void* user_data);
|
||||
private static extern unsafe byte igInputTextEx(
|
||||
byte* label,
|
||||
byte* hint,
|
||||
byte* buf,
|
||||
int buf_size,
|
||||
Vector2 size,
|
||||
ImGuiInputTextFlags flags,
|
||||
ImGuiInputTextCallback? callback,
|
||||
void* user_data
|
||||
);
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool igItemAdd(Vector4 bb, uint id, Vector4* navBb, uint flags);
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool igButtonBehavior(Vector4 bb, uint id, bool* outHovered, bool* outHeld, ImGuiButtonFlags flags);
|
||||
private static extern bool igButtonBehavior(
|
||||
Vector4 bb,
|
||||
uint id,
|
||||
bool* outHovered,
|
||||
bool* outHeld,
|
||||
ImGuiButtonFlags flags
|
||||
);
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool igItemSize_Vec2(Vector2 size, float text_baseline_y = -1.0f);
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void igRenderFrame(Vector2 p_min, Vector2 p_max, uint fill_col, bool border = true, float rounding = 0.0f);
|
||||
private static extern void igRenderFrame(
|
||||
Vector2 p_min,
|
||||
Vector2 p_max,
|
||||
uint fill_col,
|
||||
bool border = true,
|
||||
float rounding = 0.0f
|
||||
);
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void igRenderRectFilledRangeH(ImDrawList* draw_list, Vector4* rect, uint col, float x_start_norm, float x_end_norm, float rounding);
|
||||
private static extern void igRenderRectFilledRangeH(
|
||||
ImDrawList* draw_list,
|
||||
Vector4* rect,
|
||||
uint col,
|
||||
float x_start_norm,
|
||||
float x_end_norm,
|
||||
float rounding
|
||||
);
|
||||
|
||||
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern ImGuiItemFlags igGetItemFlags();
|
||||
@@ -74,7 +102,16 @@ internal static unsafe class ImGuiExtras
|
||||
#endregion
|
||||
|
||||
// Based off of code from InputTextWithHint: https://github.com/ImGuiNET/ImGui.NET/blob/069363672fed940ebdaa02f9b032c282b66467c7/src/ImGui.NET/ImGui.Manual.cs#L271
|
||||
public static unsafe bool InputTextEx(string label, string hint, ref string input, int maxLength, Vector2 size, ImGuiInputTextFlags flags = ImGuiInputTextFlags.None, ImGuiInputTextCallback? callback = null, IntPtr user_data = default)
|
||||
public static unsafe bool InputTextEx(
|
||||
string label,
|
||||
string hint,
|
||||
ref string input,
|
||||
int maxLength,
|
||||
Vector2 size,
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags.None,
|
||||
ImGuiInputTextCallback? callback = null,
|
||||
IntPtr user_data = default
|
||||
)
|
||||
{
|
||||
var utf8LabelByteCount = Encoding.UTF8.GetByteCount(label);
|
||||
byte* utf8LabelBytes;
|
||||
@@ -132,7 +169,8 @@ internal static unsafe class ImGuiExtras
|
||||
size,
|
||||
flags,
|
||||
callback,
|
||||
user_data.ToPointer());
|
||||
user_data.ToPointer()
|
||||
);
|
||||
if (!AreStringsEqual(originalUtf8InputBytes, inputBufSize, utf8InputBytes))
|
||||
{
|
||||
input = StringFromPtr(utf8InputBytes);
|
||||
@@ -155,8 +193,7 @@ internal static unsafe class ImGuiExtras
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
public static unsafe bool ItemAdd(Vector4 bb, uint id) =>
|
||||
ItemAdd(bb, id, out _);
|
||||
public static unsafe bool ItemAdd(Vector4 bb, uint id) => ItemAdd(bb, id, out _);
|
||||
|
||||
public static unsafe bool ItemAdd(Vector4 bb, uint id, out Vector4 navBb, uint flags = 0)
|
||||
{
|
||||
@@ -166,7 +203,13 @@ internal static unsafe class ImGuiExtras
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe bool ButtonBehavior(Vector4 bb, uint id, out bool hovered, out bool held, ImGuiButtonFlags flags)
|
||||
public static unsafe bool ButtonBehavior(
|
||||
Vector4 bb,
|
||||
uint id,
|
||||
out bool hovered,
|
||||
out bool held,
|
||||
ImGuiButtonFlags flags
|
||||
)
|
||||
{
|
||||
fixed (bool* hoveredPtr = &hovered)
|
||||
fixed (bool* heldPtr = &held)
|
||||
@@ -175,19 +218,34 @@ internal static unsafe class ImGuiExtras
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe void RenderFrame(Vector2 p_min, Vector2 p_max, uint fill_col, bool border = true, float rounding = 0.0f) =>
|
||||
igRenderFrame(p_min, p_max, fill_col, border, rounding);
|
||||
public static unsafe void RenderFrame(
|
||||
Vector2 p_min,
|
||||
Vector2 p_max,
|
||||
uint fill_col,
|
||||
bool border = true,
|
||||
float rounding = 0.0f
|
||||
) => igRenderFrame(p_min, p_max, fill_col, border, rounding);
|
||||
|
||||
public static unsafe void RenderRectFilledRangeH(ImDrawListPtr draw_list, Vector4 rect, uint col, float x_start_norm, float x_end_norm, float rounding) =>
|
||||
igRenderRectFilledRangeH(draw_list, &rect, col, x_start_norm, x_end_norm, rounding);
|
||||
public static unsafe void RenderRectFilledRangeH(
|
||||
ImDrawListPtr draw_list,
|
||||
Vector4 rect,
|
||||
uint col,
|
||||
float x_start_norm,
|
||||
float x_end_norm,
|
||||
float rounding
|
||||
) => igRenderRectFilledRangeH(draw_list, &rect, col, x_start_norm, x_end_norm, rounding);
|
||||
|
||||
public static unsafe bool ItemSize(Vector2 size, float text_baseline_y = -1.0f) =>
|
||||
igItemSize_Vec2(size, text_baseline_y);
|
||||
|
||||
public static unsafe ImGuiItemFlags GetItemFlags() =>
|
||||
igGetItemFlags();
|
||||
public static unsafe ImGuiItemFlags GetItemFlags() => igGetItemFlags();
|
||||
|
||||
public static unsafe int? CalcWordWrapPositionA(this ImFontPtr font, float scale, ReadOnlySpan<char> text, float wrap_width)
|
||||
public static unsafe int? CalcWordWrapPositionA(
|
||||
this ImFontPtr font,
|
||||
float scale,
|
||||
ReadOnlySpan<char> text,
|
||||
float wrap_width
|
||||
)
|
||||
{
|
||||
var utf8TextByteCount = Encoding.UTF8.GetByteCount(text);
|
||||
byte* utf8TextBytes;
|
||||
@@ -202,7 +260,13 @@ internal static unsafe class ImGuiExtras
|
||||
}
|
||||
GetUtf8(text, utf8TextBytes, utf8TextByteCount);
|
||||
|
||||
var ret = ImGuiNative.CalcWordWrapPositionA(font, scale, utf8TextBytes, utf8TextBytes + utf8TextByteCount, wrap_width);
|
||||
var ret = ImGuiNative.CalcWordWrapPositionA(
|
||||
font,
|
||||
scale,
|
||||
utf8TextBytes,
|
||||
utf8TextBytes + utf8TextByteCount,
|
||||
wrap_width
|
||||
);
|
||||
|
||||
int? retVal = null;
|
||||
if (utf8TextBytes <= ret && ret <= utf8TextBytes + utf8TextByteCount)
|
||||
@@ -217,10 +281,12 @@ internal static unsafe class ImGuiExtras
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static unsafe bool SetDragDropPayload<T>(string type, T data) where T : unmanaged =>
|
||||
public static unsafe bool SetDragDropPayload<T>(string type, T data)
|
||||
where T : unmanaged =>
|
||||
ImGui.SetDragDropPayload(type, MemoryMarshal.AsBytes(new ReadOnlySpan<T>(&data, 1)));
|
||||
|
||||
public static unsafe bool AcceptDragDropPayload<T>(string type, out T data) where T : unmanaged
|
||||
public static unsafe bool AcceptDragDropPayload<T>(string type, out T data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var payload = ImGui.AcceptDragDropPayload(type);
|
||||
if (payload.IsNull || payload.DataSize != sizeof(T))
|
||||
|
||||
+261
-76
@@ -1,11 +1,3 @@
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImPlot;
|
||||
using MathNet.Numerics.Statistics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -15,12 +7,24 @@ using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImPlot;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using MathNet.Numerics.Statistics;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
internal static class ImGuiUtils
|
||||
{
|
||||
private static readonly Stack<(Vector2 Min, Vector2 Max, float TopPadding)> GroupPanelLabelStack = new();
|
||||
private static readonly Stack<(
|
||||
Vector2 Min,
|
||||
Vector2 Max,
|
||||
float TopPadding
|
||||
)> GroupPanelLabelStack = new();
|
||||
|
||||
// Adapted from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
||||
// width = -1 -> size to parent
|
||||
@@ -62,7 +66,9 @@ internal static class ImGuiUtils
|
||||
var textFrameHeight = ImGui.GetFrameHeight();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(name);
|
||||
GroupPanelLabelStack.Push((ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), textFrameHeight / 2f)); // push rect to stack
|
||||
GroupPanelLabelStack.Push(
|
||||
(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), textFrameHeight / 2f)
|
||||
); // push rect to stack
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGui.Dummy(new Vector2(0f, textFrameHeight + itemSpacing.Y)); // shifts content by fh + is.y
|
||||
}
|
||||
@@ -112,18 +118,29 @@ internal static class ImGuiUtils
|
||||
{
|
||||
var (minClip, maxClip) = i switch
|
||||
{
|
||||
0 => (new Vector2(float.NegativeInfinity), new Vector2(labelMin.X, float.PositiveInfinity)),
|
||||
1 => (new Vector2(labelMax.X, float.NegativeInfinity), new Vector2(float.PositiveInfinity)),
|
||||
2 => (new Vector2(labelMin.X, float.NegativeInfinity), new Vector2(labelMax.X, labelMin.Y)),
|
||||
3 => (new Vector2(labelMin.X, labelMax.Y), new Vector2(labelMax.X, float.PositiveInfinity)),
|
||||
_ => (Vector2.Zero, Vector2.Zero)
|
||||
0 => (
|
||||
new Vector2(float.NegativeInfinity),
|
||||
new Vector2(labelMin.X, float.PositiveInfinity)
|
||||
),
|
||||
1 => (
|
||||
new Vector2(labelMax.X, float.NegativeInfinity),
|
||||
new Vector2(float.PositiveInfinity)
|
||||
),
|
||||
2 => (
|
||||
new Vector2(labelMin.X, float.NegativeInfinity),
|
||||
new Vector2(labelMax.X, labelMin.Y)
|
||||
),
|
||||
3 => (
|
||||
new Vector2(labelMin.X, labelMax.Y),
|
||||
new Vector2(labelMax.X, float.PositiveInfinity)
|
||||
),
|
||||
_ => (Vector2.Zero, Vector2.Zero),
|
||||
};
|
||||
|
||||
ImGui.PushClipRect(minClip, maxClip, true);
|
||||
ImGui.GetWindowDrawList().AddRect(
|
||||
innerMin, innerMax,
|
||||
ImGui.GetColorU32(ImGuiCol.Border),
|
||||
itemSpacing.X);
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRect(innerMin, innerMax, ImGui.GetColorU32(ImGuiCol.Border), itemSpacing.X);
|
||||
ImGui.PopClipRect();
|
||||
}
|
||||
|
||||
@@ -143,15 +160,23 @@ internal static class ImGuiUtils
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float Lerp(float a, float b, float t) =>
|
||||
MathF.FusedMultiplyAdd(b - a, t, a);
|
||||
private static float Lerp(float a, float b, float t) => MathF.FusedMultiplyAdd(b - a, t, a);
|
||||
|
||||
private readonly record struct ArcEdge(float Angle, Vector2 Point)
|
||||
{
|
||||
public ArcEdge(float angle) : this(angle, UnitCircle(angle)) { }
|
||||
public ArcEdge(float angle)
|
||||
: this(angle, UnitCircle(angle)) { }
|
||||
}
|
||||
|
||||
private static void ArcSegment(Vector2 o, ArcEdge prev, ArcEdge cur, ArcEdge? next, float radius, float ratio, uint color)
|
||||
private static void ArcSegment(
|
||||
Vector2 o,
|
||||
ArcEdge prev,
|
||||
ArcEdge cur,
|
||||
ArcEdge? next,
|
||||
float radius,
|
||||
float ratio,
|
||||
uint color
|
||||
)
|
||||
{
|
||||
var d = ImGui.GetWindowDrawList();
|
||||
|
||||
@@ -164,7 +189,15 @@ internal static class ImGuiUtils
|
||||
d.PathFillConvex(color);
|
||||
}
|
||||
|
||||
public static void Arc(float startAngle, float endAngle, float radius, float ratio, uint backgroundColor, uint filledColor, bool addDummy = true)
|
||||
public static void Arc(
|
||||
float startAngle,
|
||||
float endAngle,
|
||||
float radius,
|
||||
float ratio,
|
||||
uint backgroundColor,
|
||||
uint filledColor,
|
||||
bool addDummy = true
|
||||
)
|
||||
{
|
||||
// Fix normals when drawing (for antialiasing)
|
||||
if (startAngle > endAngle)
|
||||
@@ -224,9 +257,16 @@ internal static class ImGuiUtils
|
||||
ImGui.Dummy(new Vector2(radius * 2));
|
||||
}
|
||||
|
||||
public static void ArcProgress(float value, float radius, float ratio, uint backgroundColor, uint filledColor)
|
||||
public static void ArcProgress(
|
||||
float value,
|
||||
float radius,
|
||||
float ratio,
|
||||
uint backgroundColor,
|
||||
uint filledColor
|
||||
)
|
||||
{
|
||||
float startAngle, endAngle;
|
||||
float startAngle,
|
||||
endAngle;
|
||||
|
||||
// https://github.com/ocornut/imgui/commit/c895e987adf746a997b655c64a6a8916c549ff6f#diff-d750e175eb584ba76bc560b8e54cf113ccbb31dd33f75078c1588925e197a3afR1304-R1310
|
||||
if (value < 0)
|
||||
@@ -271,19 +311,34 @@ internal static class ImGuiUtils
|
||||
bar_end = bar_begin + bar_fraction;
|
||||
}
|
||||
|
||||
ImGuiExtras.RenderFrame(bbMin, bbMax, ImGui.GetColorU32(ImGuiCol.FrameBg), true, style.FrameRounding);
|
||||
ImGuiExtras.RenderFrame(
|
||||
bbMin,
|
||||
bbMax,
|
||||
ImGui.GetColorU32(ImGuiCol.FrameBg),
|
||||
true,
|
||||
style.FrameRounding
|
||||
);
|
||||
|
||||
bbMin += new Vector2(style.FrameBorderSize);
|
||||
bbMax -= new Vector2(style.FrameBorderSize);
|
||||
|
||||
ImGuiExtras.RenderRectFilledRangeH(ImGui.GetWindowDrawList(), new(bbMin.X, bbMin.Y, bbMax.X, bbMax.Y), ImGui.GetColorU32(ImGuiCol.PlotHistogram), bar_begin, bar_end, style.FrameRounding);
|
||||
ImGuiExtras.RenderRectFilledRangeH(
|
||||
ImGui.GetWindowDrawList(),
|
||||
new(bbMin.X, bbMin.Y, bbMax.X, bbMax.Y),
|
||||
ImGui.GetColorU32(ImGuiCol.PlotHistogram),
|
||||
bar_begin,
|
||||
bar_end,
|
||||
style.FrameRounding
|
||||
);
|
||||
}
|
||||
|
||||
public sealed class ViolinData
|
||||
{
|
||||
public struct Point(float x, float y, float y2)
|
||||
{
|
||||
public float X = x, Y = y, Y2 = y2;
|
||||
public float X = x,
|
||||
Y = y,
|
||||
Y2 = y2;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<Point> Data => (DataArray ?? []).AsSpan();
|
||||
@@ -291,7 +346,13 @@ internal static class ImGuiUtils
|
||||
public readonly float Min;
|
||||
public readonly float Max;
|
||||
|
||||
public ViolinData(IEnumerable<int> samples, float min, float max, int resolution, double bandwidth)
|
||||
public ViolinData(
|
||||
IEnumerable<int> samples,
|
||||
float min,
|
||||
float max,
|
||||
int resolution,
|
||||
double bandwidth
|
||||
)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
@@ -300,9 +361,12 @@ internal static class ImGuiUtils
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var s = Stopwatch.StartNew();
|
||||
var data = ParallelEnumerable.Range(0, resolution + 1)
|
||||
var data = ParallelEnumerable
|
||||
.Range(0, resolution + 1)
|
||||
.Select(n => Lerp(min, max, n / (float)resolution))
|
||||
.Select(n => (n, (float)KernelDensity.EstimateGaussian(n, bandwidth, samplesList)))
|
||||
.Select(n =>
|
||||
(n, (float)KernelDensity.EstimateGaussian(n, bandwidth, samplesList))
|
||||
)
|
||||
.Select(n => new Point(n.n, n.Item2, -n.Item2));
|
||||
// ParallelQuery doesn't support [.. data] correctly. The plots look very wrong.
|
||||
#pragma warning disable IDE0305 // Simplify collection initialization
|
||||
@@ -320,10 +384,22 @@ internal static class ImGuiUtils
|
||||
using var plotBg = ImRaii2.PushColor(ImPlotCol.Bg, Vector4.Zero);
|
||||
using var fill = ImRaii2.PushColor(ImPlotCol.Fill, Vector4.One.WithAlpha(.5f));
|
||||
|
||||
using var plot = ImRaii2.Plot("##violin", size, ImPlotFlags.CanvasOnly | ImPlotFlags.NoInputs | ImPlotFlags.NoChild | ImPlotFlags.NoFrame);
|
||||
using var plot = ImRaii2.Plot(
|
||||
"##violin",
|
||||
size,
|
||||
ImPlotFlags.CanvasOnly
|
||||
| ImPlotFlags.NoInputs
|
||||
| ImPlotFlags.NoChild
|
||||
| ImPlotFlags.NoFrame
|
||||
);
|
||||
if (plot.Success)
|
||||
{
|
||||
ImPlot.SetupAxes([], [], ImPlotAxisFlags.NoDecorations, ImPlotAxisFlags.NoDecorations | ImPlotAxisFlags.AutoFit);
|
||||
ImPlot.SetupAxes(
|
||||
[],
|
||||
[],
|
||||
ImPlotAxisFlags.NoDecorations,
|
||||
ImPlotAxisFlags.NoDecorations | ImPlotAxisFlags.AutoFit
|
||||
);
|
||||
ImPlot.SetupAxisLimits(ImAxis.X1, data.Min, data.Max, ImPlotCond.Always);
|
||||
ImPlot.SetupFinish();
|
||||
|
||||
@@ -334,14 +410,24 @@ internal static class ImGuiUtils
|
||||
var label_id = stackalloc byte[] { (byte)'\0' };
|
||||
fixed (ViolinData.Point* p = points)
|
||||
{
|
||||
ImPlot.PlotShaded(label_id, &p->X, &p->Y, &p->Y2, points.Length, ImPlotShadedFlags.None, 0, sizeof(ViolinData.Point));
|
||||
ImPlot.PlotShaded(
|
||||
label_id,
|
||||
&p->X,
|
||||
&p->Y,
|
||||
&p->Y2,
|
||||
points.Length,
|
||||
ImPlotShadedFlags.None,
|
||||
0,
|
||||
sizeof(ViolinData.Point)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SearchableComboData<T> where T : IEquatable<T>
|
||||
private sealed class SearchableComboData<T>
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
public readonly ImmutableArray<T> items;
|
||||
public List<T> filteredItems;
|
||||
@@ -381,20 +467,26 @@ internal static class ImGuiUtils
|
||||
cts = new();
|
||||
var token = cts.Token;
|
||||
task = Task.Run(() => FilterTask(inp, token), token)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (cts.IsCancellationRequested)
|
||||
return;
|
||||
.ContinueWith(
|
||||
t =>
|
||||
{
|
||||
if (cts.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Filtering recipes failed");
|
||||
}
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
try
|
||||
{
|
||||
t.Exception!.Flatten()
|
||||
.Handle(ex =>
|
||||
ex is TaskCanceledException or OperationCanceledException
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Filtering recipes failed");
|
||||
}
|
||||
},
|
||||
TaskContinuationOptions.OnlyOnFaulted
|
||||
);
|
||||
}
|
||||
|
||||
private void FilterTask(string input, CancellationToken token)
|
||||
@@ -405,7 +497,9 @@ internal static class ImGuiUtils
|
||||
return;
|
||||
}
|
||||
var matcher = new FuzzyMatcher(input.ToLowerInvariant(), MatchMode.FuzzyParts);
|
||||
var query = items.AsParallel().Select(i => (Item: i, Score: matcher.Matches(getString(i).ToLowerInvariant())))
|
||||
var query = items
|
||||
.AsParallel()
|
||||
.Select(i => (Item: i, Score: matcher.Matches(getString(i).ToLowerInvariant())))
|
||||
.Where(t => t.Score > 0)
|
||||
.OrderByDescending(t => t.Score)
|
||||
.Select(t => t.Item);
|
||||
@@ -413,16 +507,34 @@ internal static class ImGuiUtils
|
||||
filteredItems = [.. query];
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<uint, object> ComboData = [];
|
||||
|
||||
private static SearchableComboData<T> GetComboData<T>(uint comboKey, IEnumerable<T> items, T selectedItem, Func<T, string> getString) where T : IEquatable<T> =>
|
||||
private static SearchableComboData<T> GetComboData<T>(
|
||||
uint comboKey,
|
||||
IEnumerable<T> items,
|
||||
T selectedItem,
|
||||
Func<T, string> getString
|
||||
)
|
||||
where T : IEquatable<T> =>
|
||||
(SearchableComboData<T>)(
|
||||
ComboData.TryGetValue(comboKey, out var data)
|
||||
? data
|
||||
: ComboData[comboKey] = new SearchableComboData<T>(items, selectedItem, getString));
|
||||
? data
|
||||
: ComboData[comboKey] = new SearchableComboData<T>(items, selectedItem, getString)
|
||||
);
|
||||
|
||||
// https://github.com/ocornut/imgui/issues/718#issuecomment-1563162222
|
||||
public static bool SearchableCombo<T>(string id, ref T selectedItem, IEnumerable<T> items, ImFontPtr selectableFont, float width, Func<T, string> getString, Func<T, string> getId, Action<T> draw) where T : IEquatable<T>
|
||||
public static bool SearchableCombo<T>(
|
||||
string id,
|
||||
ref T selectedItem,
|
||||
IEnumerable<T> items,
|
||||
ImFontPtr selectableFont,
|
||||
float width,
|
||||
Func<T, string> getString,
|
||||
Func<T, string> getId,
|
||||
Action<T> draw
|
||||
)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
var comboKey = ImGui.GetID(id);
|
||||
var data = GetComboData(comboKey, items, selectedItem, getString);
|
||||
@@ -433,7 +545,12 @@ internal static class ImGuiUtils
|
||||
width = width == 0 ? ImGui.GetContentRegionAvail().X : width;
|
||||
var availableSpace = Math.Min(ImGui.GetContentRegionAvail().X, width);
|
||||
ImGui.SetNextItemWidth(availableSpace);
|
||||
var isInputTextEnterPressed = ImGui.InputText("##input", ref data.input, 256, ImGuiInputTextFlags.EnterReturnsTrue);
|
||||
var isInputTextEnterPressed = ImGui.InputText(
|
||||
"##input",
|
||||
ref data.input,
|
||||
256,
|
||||
ImGuiInputTextFlags.EnterReturnsTrue
|
||||
);
|
||||
var min = ImGui.GetItemRectMin();
|
||||
var size = ImGui.GetItemRectSize();
|
||||
size.X = Math.Min(size.X, availableSpace);
|
||||
@@ -447,7 +564,15 @@ internal static class ImGuiUtils
|
||||
data.wasTextActive = false;
|
||||
}
|
||||
|
||||
using (var popup = ImRaii.Popup("##popup", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings))
|
||||
using (
|
||||
var popup = ImRaii.Popup(
|
||||
"##popup",
|
||||
ImGuiWindowFlags.NoMove
|
||||
| ImGuiWindowFlags.NoResize
|
||||
| ImGuiWindowFlags.AlwaysAutoResize
|
||||
| ImGuiWindowFlags.NoSavedSettings
|
||||
)
|
||||
)
|
||||
{
|
||||
if (popup)
|
||||
{
|
||||
@@ -466,25 +591,36 @@ internal static class ImGuiUtils
|
||||
isInputTextEnterPressed = true;
|
||||
data.wasTextActive = isActive;
|
||||
|
||||
using (var scrollingRegion = ImRaii.Child("scrollingRegion", new Vector2(size.X, size.Y * 10), false, ImGuiWindowFlags.HorizontalScrollbar))
|
||||
using (
|
||||
var scrollingRegion = ImRaii.Child(
|
||||
"scrollingRegion",
|
||||
new Vector2(size.X, size.Y * 10),
|
||||
false,
|
||||
ImGuiWindowFlags.HorizontalScrollbar
|
||||
)
|
||||
)
|
||||
{
|
||||
T? _selectedItem = default;
|
||||
var height = ImGui.GetTextLineHeight();
|
||||
var r = ListClip(data.filteredItems, height, t =>
|
||||
{
|
||||
var name = getString(t);
|
||||
using (var selectFont = ImRaii.PushFont(selectableFont))
|
||||
var r = ListClip(
|
||||
data.filteredItems,
|
||||
height,
|
||||
t =>
|
||||
{
|
||||
if (ImGui.Selectable($"##{getId(t)}"))
|
||||
var name = getString(t);
|
||||
using (var selectFont = ImRaii.PushFont(selectableFont))
|
||||
{
|
||||
_selectedItem = t;
|
||||
return true;
|
||||
if (ImGui.Selectable($"##{getId(t)}"))
|
||||
{
|
||||
_selectedItem = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X / 2f);
|
||||
draw(t);
|
||||
return false;
|
||||
}
|
||||
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X / 2f);
|
||||
draw(t);
|
||||
return false;
|
||||
});
|
||||
);
|
||||
if (r)
|
||||
{
|
||||
selectedItem = _selectedItem!;
|
||||
@@ -530,7 +666,11 @@ internal static class ImGuiUtils
|
||||
imGuiListClipperPtr.Begin(data.Count, lineHeight);
|
||||
while (imGuiListClipperPtr.Step())
|
||||
{
|
||||
for (var i = imGuiListClipperPtr.DisplayStart; i <= imGuiListClipperPtr.DisplayEnd; i++)
|
||||
for (
|
||||
var i = imGuiListClipperPtr.DisplayStart;
|
||||
i <= imGuiListClipperPtr.DisplayEnd;
|
||||
i++
|
||||
)
|
||||
{
|
||||
if (i >= data.Count)
|
||||
return false;
|
||||
@@ -551,10 +691,28 @@ internal static class ImGuiUtils
|
||||
}
|
||||
}
|
||||
|
||||
public static bool InputTextMultilineWithHint(string label, string hint, ref string input, int maxLength, Vector2 size, ImGuiInputTextFlags flags = ImGuiInputTextFlags.None, ImGuiInputTextCallback? callback = null, IntPtr user_data = default)
|
||||
public static bool InputTextMultilineWithHint(
|
||||
string label,
|
||||
string hint,
|
||||
ref string input,
|
||||
int maxLength,
|
||||
Vector2 size,
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags.None,
|
||||
ImGuiInputTextCallback? callback = null,
|
||||
IntPtr user_data = default
|
||||
)
|
||||
{
|
||||
const ImGuiInputTextFlags Multiline = (ImGuiInputTextFlags)(1 << 26);
|
||||
return ImGuiExtras.InputTextEx(label, hint, ref input, maxLength, size, flags | Multiline, callback, user_data);
|
||||
return ImGuiExtras.InputTextEx(
|
||||
label,
|
||||
hint,
|
||||
ref input,
|
||||
maxLength,
|
||||
size,
|
||||
flags | Multiline,
|
||||
callback,
|
||||
user_data
|
||||
);
|
||||
}
|
||||
|
||||
private static Vector2 GetIconSize(FontAwesomeIcon icon)
|
||||
@@ -563,7 +721,12 @@ internal static class ImGuiUtils
|
||||
return ImGui.CalcTextSize(icon.ToIconString());
|
||||
}
|
||||
|
||||
private static void DrawCenteredIcon(FontAwesomeIcon icon, Vector2 offset, Vector2 size, bool isDisabled = false)
|
||||
private static void DrawCenteredIcon(
|
||||
FontAwesomeIcon icon,
|
||||
Vector2 offset,
|
||||
Vector2 size,
|
||||
bool isDisabled = false
|
||||
)
|
||||
{
|
||||
var iconSize = GetIconSize(icon);
|
||||
|
||||
@@ -585,7 +748,15 @@ internal static class ImGuiUtils
|
||||
iconOffset = Vector2.Zero;
|
||||
}
|
||||
|
||||
ImGui.GetWindowDrawList().AddText(UiBuilder.IconFont, UiBuilder.IconFont.FontSize * ImGuiHelpers.GlobalScale * scale, offset + iconOffset, ImGui.GetColorU32(!isDisabled ? ImGuiCol.Text : ImGuiCol.TextDisabled), icon.ToIconString());
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddText(
|
||||
UiBuilder.IconFont,
|
||||
UiBuilder.IconFont.FontSize * ImGuiHelpers.GlobalScale * scale,
|
||||
offset + iconOffset,
|
||||
ImGui.GetColorU32(!isDisabled ? ImGuiCol.Text : ImGuiCol.TextDisabled),
|
||||
icon.ToIconString()
|
||||
);
|
||||
}
|
||||
|
||||
public static bool IconButtonSquare(FontAwesomeIcon icon, float size = -1)
|
||||
@@ -627,7 +798,13 @@ internal static class ImGuiUtils
|
||||
Dalamud.Utility.Util.OpenLink(url);
|
||||
var urlWithoutScheme = url;
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
||||
urlWithoutScheme = uri.Host + (string.Equals(uri.PathAndQuery, "/", StringComparison.Ordinal) ? string.Empty : uri.PathAndQuery);
|
||||
urlWithoutScheme =
|
||||
uri.Host
|
||||
+ (
|
||||
string.Equals(uri.PathAndQuery, "/", StringComparison.Ordinal)
|
||||
? string.Empty
|
||||
: uri.PathAndQuery
|
||||
);
|
||||
Tooltip(urlWithoutScheme);
|
||||
}
|
||||
}
|
||||
@@ -647,7 +824,11 @@ internal static class ImGuiUtils
|
||||
ImGui.TextUnformatted(text);
|
||||
}
|
||||
|
||||
public static void TextWrappedTo(string text, float wrapPosX = default, float basePosX = default)
|
||||
public static void TextWrappedTo(
|
||||
string text,
|
||||
float wrapPosX = default,
|
||||
float basePosX = default
|
||||
)
|
||||
{
|
||||
var font = ImGui.GetFont();
|
||||
|
||||
@@ -663,7 +844,11 @@ internal static class ImGuiUtils
|
||||
currentWrapWidth = wrapPosX - currentPos;
|
||||
|
||||
var textBuf = text.AsSpan();
|
||||
var lineSize = font.CalcWordWrapPositionA(ImGuiHelpers.GlobalScale, textBuf, currentWrapWidth);
|
||||
var lineSize = font.CalcWordWrapPositionA(
|
||||
ImGuiHelpers.GlobalScale,
|
||||
textBuf,
|
||||
currentWrapWidth
|
||||
);
|
||||
if (lineSize == 0)
|
||||
lineSize = textBuf.Length;
|
||||
var lineBuf = textBuf[..lineSize];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImPlot;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImPlot;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
@@ -61,7 +61,10 @@ public static class ImRaii2
|
||||
|
||||
public static IEndObject Plot(string title_id, Vector2 size, ImPlotFlags flags)
|
||||
{
|
||||
return new EndConditionally(new Action(ImPlot.EndPlot), ImPlot.BeginPlot(title_id, size, flags));
|
||||
return new EndConditionally(
|
||||
new Action(ImPlot.EndPlot),
|
||||
ImPlot.BeginPlot(title_id, size, flags)
|
||||
);
|
||||
}
|
||||
|
||||
public static IEndObject PushStyle(ImPlotStyleVar idx, Vector2 val)
|
||||
|
||||
@@ -10,18 +10,24 @@ public static class LuminaSheets
|
||||
|
||||
public static readonly ExcelSheet<Recipe> RecipeSheet = Module.GetSheet<Recipe>();
|
||||
public static readonly ExcelSheet<Action> ActionSheet = Module.GetSheet<Action>();
|
||||
public static readonly ExcelSheet<CraftAction> CraftActionSheet = Module.GetSheet<CraftAction>();
|
||||
public static readonly ExcelSheet<CraftAction> CraftActionSheet =
|
||||
Module.GetSheet<CraftAction>();
|
||||
public static readonly ExcelSheet<Status> StatusSheet = Module.GetSheet<Status>();
|
||||
public static readonly ExcelSheet<Addon> AddonSheet = Module.GetSheet<Addon>();
|
||||
public static readonly ExcelSheet<ClassJob> ClassJobSheet = Module.GetSheet<ClassJob>();
|
||||
public static readonly ExcelSheet<Item> ItemSheet = Module.GetSheet<Item>();
|
||||
public static readonly ExcelSheet<Item> ItemSheetEnglish = Module.GetSheet<Item>(Language.English)!;
|
||||
public static readonly ExcelSheet<Item> ItemSheetEnglish = Module.GetSheet<Item>(
|
||||
Language.English
|
||||
)!;
|
||||
public static readonly ExcelSheet<Level> LevelSheet = Module.GetSheet<Level>();
|
||||
public static readonly ExcelSheet<Quest> QuestSheet = Module.GetSheet<Quest>();
|
||||
public static readonly ExcelSheet<Materia> MateriaSheet = Module.GetSheet<Materia>();
|
||||
public static readonly ExcelSheet<BaseParam> BaseParamSheet = Module.GetSheet<BaseParam>();
|
||||
public static readonly ExcelSheet<ItemFood> ItemFoodSheet = Module.GetSheet<ItemFood>();
|
||||
public static readonly ExcelSheet<WKSMissionToDoEvalutionRefin> WKSMissionToDoEvalutionRefinSheet = Module.GetSheet<WKSMissionToDoEvalutionRefin>();
|
||||
public static readonly ExcelSheet<RecipeLevelTable> RecipeLevelTableSheet = Module.GetSheet<RecipeLevelTable>();
|
||||
public static readonly ExcelSheet<GathererCrafterLvAdjustTable> GathererCrafterLvAdjustTableSheet = Module.GetSheet<GathererCrafterLvAdjustTable>();
|
||||
public static readonly ExcelSheet<WKSMissionToDoEvalutionRefin> WKSMissionToDoEvalutionRefinSheet =
|
||||
Module.GetSheet<WKSMissionToDoEvalutionRefin>();
|
||||
public static readonly ExcelSheet<RecipeLevelTable> RecipeLevelTableSheet =
|
||||
Module.GetSheet<RecipeLevelTable>();
|
||||
public static readonly ExcelSheet<GathererCrafterLvAdjustTable> GathererCrafterLvAdjustTableSheet =
|
||||
Module.GetSheet<GathererCrafterLvAdjustTable>();
|
||||
}
|
||||
|
||||
+81
-37
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Craftimizer.Plugin.Windows;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
@@ -6,9 +9,6 @@ using Craftimizer.Windows;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
@@ -50,9 +50,13 @@ public sealed class Plugin : IDalamudPlugin
|
||||
AttributeCommandManager = new();
|
||||
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
Version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.Split('+')[0];
|
||||
Version = assembly
|
||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
|
||||
.InformationalVersion.Split('+')[0];
|
||||
Author = assembly.GetCustomAttribute<AssemblyCompanyAttribute>()!.Company;
|
||||
BuildConfiguration = assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()!.Configuration;
|
||||
BuildConfiguration = assembly
|
||||
.GetCustomAttribute<AssemblyConfigurationAttribute>()!
|
||||
.Configuration;
|
||||
if (DateTime.Now is { Day: 1, Month: 4 })
|
||||
Icon = IconManager.GetAssemblyTexture("Graphics.horse_icon.png");
|
||||
else
|
||||
@@ -72,59 +76,93 @@ public sealed class Plugin : IDalamudPlugin
|
||||
Service.PluginInterface.UiBuilder.OpenMainUi += OpenCraftingLog;
|
||||
}
|
||||
|
||||
public (CharacterStats? Character, RecipeData? Recipe, MacroEditor.CrafterBuffs? Buffs) GetOpenedStats()
|
||||
public (
|
||||
CharacterStats? Character,
|
||||
RecipeData? Recipe,
|
||||
MacroEditor.CrafterBuffs? Buffs
|
||||
) GetOpenedStats()
|
||||
{
|
||||
var editorWindow = (EditorWindow?.IsOpen ?? false) ? EditorWindow : null;
|
||||
var recipeData = editorWindow?.RecipeData ?? Service.Plugin.RecipeNoteWindow.RecipeData;
|
||||
var characterStats = editorWindow?.CharacterStats ?? Service.Plugin.RecipeNoteWindow.CharacterStats;
|
||||
var buffs = editorWindow?.Buffs ?? (RecipeNoteWindow.CharacterStats != null ? new(Service.Objects.LocalPlayer?.StatusList) : null);
|
||||
var characterStats =
|
||||
editorWindow?.CharacterStats ?? Service.Plugin.RecipeNoteWindow.CharacterStats;
|
||||
var buffs =
|
||||
editorWindow?.Buffs
|
||||
?? (
|
||||
RecipeNoteWindow.CharacterStats != null
|
||||
? new(Service.Objects.LocalPlayer?.StatusList)
|
||||
: null
|
||||
);
|
||||
|
||||
return (characterStats, recipeData, buffs);
|
||||
}
|
||||
|
||||
public (CharacterStats Character, RecipeData Recipe, MacroEditor.CrafterBuffs Buffs) GetDefaultStats()
|
||||
public (
|
||||
CharacterStats Character,
|
||||
RecipeData Recipe,
|
||||
MacroEditor.CrafterBuffs Buffs
|
||||
) GetDefaultStats()
|
||||
{
|
||||
var stats = GetOpenedStats();
|
||||
return (
|
||||
stats.Character ?? new()
|
||||
{
|
||||
Craftsmanship = 100,
|
||||
Control = 100,
|
||||
CP = 200,
|
||||
Level = 10,
|
||||
CanUseManipulation = false,
|
||||
HasSplendorousBuff = false,
|
||||
IsSpecialist = false,
|
||||
},
|
||||
stats.Character
|
||||
?? new()
|
||||
{
|
||||
Craftsmanship = 100,
|
||||
Control = 100,
|
||||
CP = 200,
|
||||
Level = 10,
|
||||
CanUseManipulation = false,
|
||||
HasSplendorousBuff = false,
|
||||
IsSpecialist = false,
|
||||
},
|
||||
stats.Recipe ?? new(1023),
|
||||
stats.Buffs ?? new(null)
|
||||
);
|
||||
}
|
||||
|
||||
[Command(name: "/crafteditor", aliases: "/macroeditor", description: "Open the crafting macro editor.")]
|
||||
[Command(
|
||||
name: "/crafteditor",
|
||||
aliases: "/macroeditor",
|
||||
description: "Open the crafting macro editor."
|
||||
)]
|
||||
public void OpenEmptyMacroEditor()
|
||||
{
|
||||
var stats = GetDefaultStats();
|
||||
OpenMacroEditor(stats.Character, stats.Recipe, stats.Buffs, null, [], null);
|
||||
}
|
||||
|
||||
public void OpenMacroEditor(CharacterStats characterStats, RecipeData recipeData, MacroEditor.CrafterBuffs buffs, IEnumerable<int>? ingredientHqCounts, IEnumerable<ActionType> actions, Action<IEnumerable<ActionType>>? setter)
|
||||
public void OpenMacroEditor(
|
||||
CharacterStats characterStats,
|
||||
RecipeData recipeData,
|
||||
MacroEditor.CrafterBuffs buffs,
|
||||
IEnumerable<int>? ingredientHqCounts,
|
||||
IEnumerable<ActionType> actions,
|
||||
Action<IEnumerable<ActionType>>? setter
|
||||
)
|
||||
{
|
||||
EditorWindow?.Dispose();
|
||||
EditorWindow = new(characterStats, recipeData, buffs, ingredientHqCounts, actions, setter);
|
||||
}
|
||||
|
||||
[Command(name: "/craftaction", description: "Execute the suggested action in the synthesis helper. Can also be run inside a macro. This command is useful for controller players.")]
|
||||
public void ExecuteSuggestedSynthHelperAction() =>
|
||||
SynthHelperWindow.ExecuteNextAction();
|
||||
[Command(
|
||||
name: "/craftaction",
|
||||
description: "Execute the suggested action in the synthesis helper. Can also be run inside a macro. This command is useful for controller players."
|
||||
)]
|
||||
public void ExecuteSuggestedSynthHelperAction() => SynthHelperWindow.ExecuteNextAction();
|
||||
|
||||
[Command(name: "/craftretry", description: "Clicks \"Retry\" in the synthesis helper. Can also be run inside a macro. This command is useful for controller players.")]
|
||||
public void ExecuteRetrySynthHelper() =>
|
||||
SynthHelperWindow.AttemptRetry();
|
||||
[Command(
|
||||
name: "/craftretry",
|
||||
description: "Clicks \"Retry\" in the synthesis helper. Can also be run inside a macro. This command is useful for controller players."
|
||||
)]
|
||||
public void ExecuteRetrySynthHelper() => SynthHelperWindow.AttemptRetry();
|
||||
|
||||
[Command(name: "/craftimizer", aliases: "/forgeimizer", description: "Open the settings window.")]
|
||||
private void OpenSettingsWindowForced() =>
|
||||
OpenSettingsWindow(true);
|
||||
[Command(
|
||||
name: "/craftimizer",
|
||||
aliases: "/forgeimizer",
|
||||
description: "Open the settings window."
|
||||
)]
|
||||
private void OpenSettingsWindowForced() => OpenSettingsWindow(true);
|
||||
|
||||
public void OpenSettingsWindow(bool force = false)
|
||||
{
|
||||
@@ -138,7 +176,11 @@ public sealed class Plugin : IDalamudPlugin
|
||||
SettingsWindow.SelectTab(selectedTabLabel);
|
||||
}
|
||||
|
||||
[Command(name: "/craftmacros", aliases: "/macrolist", description: "Open the crafting macros window.")]
|
||||
[Command(
|
||||
name: "/craftmacros",
|
||||
aliases: "/macrolist",
|
||||
description: "Open the crafting macros window."
|
||||
)]
|
||||
public void OpenMacroListWindow()
|
||||
{
|
||||
ListWindow.IsOpen = true;
|
||||
@@ -157,12 +199,14 @@ public sealed class Plugin : IDalamudPlugin
|
||||
}
|
||||
|
||||
public static IActiveNotification DisplaySolverWarning(string text) =>
|
||||
DisplayNotification(new()
|
||||
{
|
||||
Content = text,
|
||||
Title = "Solver Warning",
|
||||
Type = NotificationType.Warning
|
||||
});
|
||||
DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content = text,
|
||||
Title = "Solver Warning",
|
||||
Type = NotificationType.Warning,
|
||||
}
|
||||
);
|
||||
|
||||
public static IActiveNotification DisplayNotification(Notification notification)
|
||||
{
|
||||
|
||||
+47
-16
@@ -14,22 +14,53 @@ namespace Craftimizer.Plugin;
|
||||
public sealed class Service
|
||||
{
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
[PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; }
|
||||
[PluginService] public static ICommandManager CommandManager { get; private set; }
|
||||
[PluginService] public static IObjectTable Objects { get; private set; }
|
||||
[PluginService] public static ISigScanner SigScanner { get; private set; }
|
||||
[PluginService] public static IGameGui GameGui { get; private set; }
|
||||
[PluginService] public static IClientState ClientState { get; private set; }
|
||||
[PluginService] public static IDataManager DataManager { get; private set; }
|
||||
[PluginService] public static ITextureProvider TextureProvider { get; private set; }
|
||||
[PluginService] public static IDalamudAssetManager DalamudAssetManager { get; private set; }
|
||||
[PluginService] public static ITargetManager TargetManager { get; private set; }
|
||||
[PluginService] public static ICondition Condition { get; private set; }
|
||||
[PluginService] public static IFramework Framework { get; private set; }
|
||||
[PluginService] public static IPluginLog PluginLog { get; private set; }
|
||||
[PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; }
|
||||
[PluginService] public static INotificationManager NotificationManager { get; private set; }
|
||||
[PluginService] public static ISeStringEvaluator SeStringEvaluator { get; private set; }
|
||||
[PluginService]
|
||||
public static IDalamudPluginInterface PluginInterface { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static ICommandManager CommandManager { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IObjectTable Objects { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static ISigScanner SigScanner { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IGameGui GameGui { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IClientState ClientState { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IDataManager DataManager { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static ITextureProvider TextureProvider { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IDalamudAssetManager DalamudAssetManager { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static ITargetManager TargetManager { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static ICondition Condition { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IFramework Framework { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IPluginLog PluginLog { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static IGameInteropProvider GameInteropProvider { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static INotificationManager NotificationManager { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
public static ISeStringEvaluator SeStringEvaluator { get; private set; }
|
||||
|
||||
public static Plugin Plugin { get; private set; }
|
||||
public static Configuration Configuration => Plugin.Configuration;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using Action = Lumina.Excel.Sheets.Action;
|
||||
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||
using Condition = Craftimizer.Simulator.Condition;
|
||||
using Status = Lumina.Excel.Sheets.Status;
|
||||
using Craftimizer.Utils;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
@@ -27,7 +27,10 @@ internal static class ActionUtils
|
||||
{
|
||||
var actionTypes = Enum.GetValues<ActionType>();
|
||||
var classJobs = Enum.GetValues<ClassJob>();
|
||||
ActionRows = new (CraftAction? CraftAction, Action? Action)[actionTypes.Length, classJobs.Length];
|
||||
ActionRows = new (CraftAction? CraftAction, Action? Action)[
|
||||
actionTypes.Length,
|
||||
classJobs.Length
|
||||
];
|
||||
foreach (var actionType in actionTypes)
|
||||
{
|
||||
var actionId = actionType.Base().ActionId;
|
||||
@@ -35,37 +38,48 @@ internal static class ActionUtils
|
||||
{
|
||||
foreach (var classJob in classJobs)
|
||||
{
|
||||
ActionRows[(int)actionType, (int)classJob] = (classJob switch
|
||||
{
|
||||
ClassJob.Carpenter => baseCraftAction.CRP.Value,
|
||||
ClassJob.Blacksmith => baseCraftAction.BSM.Value,
|
||||
ClassJob.Armorer => baseCraftAction.ARM.Value,
|
||||
ClassJob.Goldsmith => baseCraftAction.GSM.Value,
|
||||
ClassJob.Leatherworker => baseCraftAction.LTW.Value,
|
||||
ClassJob.Weaver => baseCraftAction.WVR.Value,
|
||||
ClassJob.Alchemist => baseCraftAction.ALC.Value,
|
||||
ClassJob.Culinarian => baseCraftAction.CUL.Value,
|
||||
_ => baseCraftAction
|
||||
}, null);
|
||||
ActionRows[(int)actionType, (int)classJob] = (
|
||||
classJob switch
|
||||
{
|
||||
ClassJob.Carpenter => baseCraftAction.CRP.Value,
|
||||
ClassJob.Blacksmith => baseCraftAction.BSM.Value,
|
||||
ClassJob.Armorer => baseCraftAction.ARM.Value,
|
||||
ClassJob.Goldsmith => baseCraftAction.GSM.Value,
|
||||
ClassJob.Leatherworker => baseCraftAction.LTW.Value,
|
||||
ClassJob.Weaver => baseCraftAction.WVR.Value,
|
||||
ClassJob.Alchemist => baseCraftAction.ALC.Value,
|
||||
ClassJob.Culinarian => baseCraftAction.CUL.Value,
|
||||
_ => baseCraftAction,
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
if (LuminaSheets.ActionSheet.GetRowOrDefault(actionId) is { } baseAction)
|
||||
{
|
||||
var possibleActions = LuminaSheets.ActionSheet.Where(r =>
|
||||
r.Icon == baseAction.Icon &&
|
||||
r.ActionCategory.RowId == baseAction.ActionCategory.RowId &&
|
||||
r.Name.Equals(baseAction.Name));
|
||||
r.Icon == baseAction.Icon
|
||||
&& r.ActionCategory.RowId == baseAction.ActionCategory.RowId
|
||||
&& r.Name.Equals(baseAction.Name)
|
||||
);
|
||||
|
||||
foreach (var classJob in classJobs)
|
||||
ActionRows[(int)actionType, (int)classJob] = (null, possibleActions.First(r => r.ClassJobCategory.ValueNullable?.IsClassJob(classJob) ?? false));
|
||||
ActionRows[(int)actionType, (int)classJob] = (
|
||||
null,
|
||||
possibleActions.First(r =>
|
||||
r.ClassJobCategory.ValueNullable?.IsClassJob(classJob) ?? false
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize() { }
|
||||
|
||||
public static (CraftAction? CraftAction, Action? Action) GetActionRow(this ActionType me, ClassJob classJob) =>
|
||||
ActionRows[(int)me, (int)classJob];
|
||||
public static (CraftAction? CraftAction, Action? Action) GetActionRow(
|
||||
this ActionType me,
|
||||
ClassJob classJob
|
||||
) => ActionRows[(int)me, (int)classJob];
|
||||
|
||||
public static uint GetId(this ActionType me, ClassJob classJob)
|
||||
{
|
||||
@@ -86,7 +100,11 @@ internal static class ActionUtils
|
||||
return Service.IconManager.GetIconCached(craftAction?.Icon ?? action?.Icon ?? 1953);
|
||||
}
|
||||
|
||||
public static ActionType? GetActionTypeFromId(uint actionId, ClassJob classJob, bool isCraftAction)
|
||||
public static ActionType? GetActionTypeFromId(
|
||||
uint actionId,
|
||||
ClassJob classJob,
|
||||
bool isCraftAction
|
||||
)
|
||||
{
|
||||
foreach (var action in Enum.GetValues<ActionType>())
|
||||
{
|
||||
@@ -119,7 +137,7 @@ internal static class ClassJobUtils
|
||||
ClassJob.Weaver => 13,
|
||||
ClassJob.Alchemist => 14,
|
||||
ClassJob.Culinarian => 15,
|
||||
_ => 0
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static ClassJob? GetClassJobFromIdx(byte classJobIdx) =>
|
||||
@@ -133,7 +151,7 @@ internal static class ClassJobUtils
|
||||
13 => ClassJob.Weaver,
|
||||
14 => ClassJob.Alchemist,
|
||||
15 => ClassJob.Culinarian,
|
||||
_ => null
|
||||
_ => null,
|
||||
};
|
||||
|
||||
public static sbyte GetExpArrayIdx(this ClassJob me) =>
|
||||
@@ -158,7 +176,11 @@ internal static class ClassJobUtils
|
||||
}
|
||||
|
||||
public static unsafe bool CanPlayerUseManipulation(this ClassJob me) =>
|
||||
UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(ActionType.Manipulation.GetActionRow(me).Action!.Value.UnlockLink.RowId);
|
||||
UIState
|
||||
.Instance()
|
||||
->IsUnlockLinkUnlockedOrQuestCompleted(
|
||||
ActionType.Manipulation.GetActionRow(me).Action!.Value.UnlockLink.RowId
|
||||
);
|
||||
|
||||
public static string GetName(this ClassJob me)
|
||||
{
|
||||
@@ -186,8 +208,7 @@ internal static class ClassJobUtils
|
||||
public static Quest GetUnlockQuest(this ClassJob me) =>
|
||||
LuminaSheets.QuestSheet.GetRow(65720 + (uint)me);
|
||||
|
||||
public static ushort GetIconId(this ClassJob me) =>
|
||||
(ushort)(62000 + me.GetClassJobIndex());
|
||||
public static ushort GetIconId(this ClassJob me) => (ushort)(62000 + me.GetClassJobIndex());
|
||||
|
||||
public static bool IsClassJob(this ClassJobCategory me, ClassJob classJob) =>
|
||||
classJob switch
|
||||
@@ -200,7 +221,7 @@ internal static class ClassJobUtils
|
||||
ClassJob.Weaver => me.WVR,
|
||||
ClassJob.Alchemist => me.ALC,
|
||||
ClassJob.Culinarian => me.CUL,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,7 +240,7 @@ internal static class ConditionUtils
|
||||
Condition.Malleable => (13455, 14208),
|
||||
Condition.Primed => (13454, 14207),
|
||||
Condition.GoodOmen => (14214, 14215),
|
||||
_ => (226, 14200) // Unknown
|
||||
_ => (226, 14200), // Unknown
|
||||
};
|
||||
|
||||
private static Vector3 AddRGB(this Condition me) =>
|
||||
@@ -235,10 +256,11 @@ internal static class ConditionUtils
|
||||
Condition.Malleable => new(-80, -40, 180),
|
||||
Condition.Primed => new(30, -155, 200),
|
||||
Condition.GoodOmen => new(100, 20, 0),
|
||||
_ => Vector3.Zero // Unknown
|
||||
_ => Vector3.Zero, // Unknown
|
||||
};
|
||||
|
||||
private const float ConditionCyclePeriod = 19 / 30f;
|
||||
|
||||
// The real period of all condition color cycles are 0.633... (19/30) seconds
|
||||
// Interp accepts 0-1
|
||||
public static Vector4 GetColor(this Condition me, float interp)
|
||||
@@ -252,12 +274,32 @@ internal static class ConditionUtils
|
||||
addRgb = interp switch
|
||||
{
|
||||
< 0.155f => Vector3.Lerp(new(128, 0, 0), new(128, 80, 0), (interp - 0) / 0.155f),
|
||||
< 0.315f => Vector3.Lerp(new(128, 80, 0), new(128, 128, 0), (interp - 0.155f) / 0.16f),
|
||||
< 0.475f => Vector3.Lerp(new(128, 128, 0), new(0, 64, 0), (interp - 0.315f) / 0.16f),
|
||||
< 0.630f => Vector3.Lerp(new(0, 64, 0), new(0, 128, 128), (interp - 0.475f) / 0.155f),
|
||||
< 0.790f => Vector3.Lerp(new(0, 128, 128), new(0, 0, 128), (interp - 0.630f) / 0.16f),
|
||||
< 0.945f => Vector3.Lerp(new(0, 0, 128), new(64, 0, 64), (interp - 0.790f) / 0.155f),
|
||||
_ => new(64, 0, 64)
|
||||
< 0.315f => Vector3.Lerp(
|
||||
new(128, 80, 0),
|
||||
new(128, 128, 0),
|
||||
(interp - 0.155f) / 0.16f
|
||||
),
|
||||
< 0.475f => Vector3.Lerp(
|
||||
new(128, 128, 0),
|
||||
new(0, 64, 0),
|
||||
(interp - 0.315f) / 0.16f
|
||||
),
|
||||
< 0.630f => Vector3.Lerp(
|
||||
new(0, 64, 0),
|
||||
new(0, 128, 128),
|
||||
(interp - 0.475f) / 0.155f
|
||||
),
|
||||
< 0.790f => Vector3.Lerp(
|
||||
new(0, 128, 128),
|
||||
new(0, 0, 128),
|
||||
(interp - 0.630f) / 0.16f
|
||||
),
|
||||
< 0.945f => Vector3.Lerp(
|
||||
new(0, 0, 128),
|
||||
new(64, 0, 64),
|
||||
(interp - 0.790f) / 0.155f
|
||||
),
|
||||
_ => new(64, 0, 64),
|
||||
};
|
||||
}
|
||||
// Period is twice as fast so we oscillate at twice that speed
|
||||
@@ -285,7 +327,7 @@ internal static class ConditionUtils
|
||||
Condition.Pliant => Vector3.Lerp(new(0, 150, 0), new(0, 249, 0), interp),
|
||||
Condition.Primed => Vector3.Lerp(new(-30, -255, 50), new(29, -156, 199), interp),
|
||||
Condition.GoodOmen => Vector3.Lerp(new(100, 20, 0), new(100, 99, 99), interp),
|
||||
_ => default
|
||||
_ => default,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -294,7 +336,9 @@ internal static class ConditionUtils
|
||||
|
||||
public static Vector4 GetColor(this Condition me, TimeSpan time)
|
||||
{
|
||||
return me.GetColor((float)(time.TotalSeconds % ConditionCyclePeriod / ConditionCyclePeriod));
|
||||
return me.GetColor(
|
||||
(float)(time.TotalSeconds % ConditionCyclePeriod / ConditionCyclePeriod)
|
||||
);
|
||||
}
|
||||
|
||||
public static string Name(this Condition me) =>
|
||||
@@ -310,7 +354,11 @@ internal static class ConditionUtils
|
||||
foreach (var payload in text)
|
||||
{
|
||||
if (payload is { Type: ReadOnlySePayloadType.Macro, MacroCode: MacroCode.Float })
|
||||
finalText += new ReadOnlySePayload(ReadOnlySePayloadType.Text, default, Encoding.UTF8.GetBytes(isRelic ? "1.75" : "1.5"));
|
||||
finalText += new ReadOnlySePayload(
|
||||
ReadOnlySePayloadType.Text,
|
||||
default,
|
||||
Encoding.UTF8.GetBytes(isRelic ? "1.75" : "1.5")
|
||||
);
|
||||
else
|
||||
finalText += payload;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Game.Command;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Game.Command;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class CommandAttribute(string name, string description, bool hidden = false, params string[] aliases) : Attribute
|
||||
public sealed class CommandAttribute(
|
||||
string name,
|
||||
string description,
|
||||
bool hidden = false,
|
||||
params string[] aliases
|
||||
) : Attribute
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public string Description { get; } = description;
|
||||
@@ -22,7 +27,11 @@ public sealed class AttributeCommandManager : IDisposable
|
||||
public AttributeCommandManager()
|
||||
{
|
||||
var target = Service.Plugin;
|
||||
foreach (var method in target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
foreach (
|
||||
var method in target
|
||||
.GetType()
|
||||
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
)
|
||||
{
|
||||
if (method.GetCustomAttribute<CommandAttribute>() is not { } command)
|
||||
continue;
|
||||
@@ -51,15 +60,21 @@ public sealed class AttributeCommandManager : IDisposable
|
||||
};
|
||||
|
||||
if (!RegisteredCommands.Add(command.Name))
|
||||
throw new InvalidOperationException($"Command '{command.Name}' is already registered.");
|
||||
throw new InvalidOperationException(
|
||||
$"Command '{command.Name}' is already registered."
|
||||
);
|
||||
|
||||
if (!Service.CommandManager.AddHandler(command.Name, info))
|
||||
throw new InvalidOperationException($"Failed to register command '{command.Name}'.");
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to register command '{command.Name}'."
|
||||
);
|
||||
|
||||
foreach (var alias in command.Aliases)
|
||||
{
|
||||
if (!RegisteredCommands.Add(alias))
|
||||
throw new InvalidOperationException($"Command '{alias}' is already registered.");
|
||||
throw new InvalidOperationException(
|
||||
$"Command '{alias}' is already registered."
|
||||
);
|
||||
|
||||
if (!Service.CommandManager.AddHandler(alias, aliasInfo))
|
||||
throw new InvalidOperationException($"Failed to register command '{alias}'.");
|
||||
|
||||
@@ -4,7 +4,8 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
public sealed class BackgroundTask<T>(Func<CancellationToken, T> func) : IDisposable where T : struct
|
||||
public sealed class BackgroundTask<T>(Func<CancellationToken, T> func) : IDisposable
|
||||
where T : struct
|
||||
{
|
||||
public T? Result { get; private set; }
|
||||
public Exception? Exception { get; private set; }
|
||||
@@ -19,26 +20,28 @@ public sealed class BackgroundTask<T>(Func<CancellationToken, T> func) : IDispos
|
||||
var token = TokenSource.Token;
|
||||
var task = Task.Run(() => Result = Func(token), token);
|
||||
_ = task.ContinueWith(t => Completed = true);
|
||||
_ = task.ContinueWith(t =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
_ = task.ContinueWith(
|
||||
t =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Exception = e;
|
||||
Log.Error(e, "Background task failed");
|
||||
}
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
try
|
||||
{
|
||||
t.Exception!.Flatten()
|
||||
.Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Exception = e;
|
||||
Log.Error(e, "Background task failed");
|
||||
}
|
||||
},
|
||||
TaskContinuationOptions.OnlyOnFaulted
|
||||
);
|
||||
}
|
||||
|
||||
public void Cancel() =>
|
||||
TokenSource.Cancel();
|
||||
public void Cancel() => TokenSource.Cancel();
|
||||
|
||||
public void Dispose() =>
|
||||
Cancel();
|
||||
public void Dispose() => Cancel();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 2880)]
|
||||
public unsafe struct CSRecipeNote
|
||||
{
|
||||
[FieldOffset(0x118)] public ushort ActiveCraftRecipeId;
|
||||
[FieldOffset(0x118)]
|
||||
public ushort ActiveCraftRecipeId;
|
||||
|
||||
public static CSRecipeNote* Instance()
|
||||
{
|
||||
|
||||
+23
-11
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using System;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -19,16 +19,28 @@ public static unsafe class Chat
|
||||
|
||||
var unsanitizedLength = str->Length;
|
||||
str->SanitizeString(
|
||||
AllowedEntities.Unknown9 | // 200
|
||||
AllowedEntities.Payloads | // 40
|
||||
AllowedEntities.OtherCharacters | // 20
|
||||
AllowedEntities.CharacterList | // 10
|
||||
AllowedEntities.SpecialCharacters | // 8
|
||||
AllowedEntities.Numbers | // 4
|
||||
AllowedEntities.LowercaseLetters | // 2
|
||||
AllowedEntities.UppercaseLetters, // 1
|
||||
null);
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(unsanitizedLength, str->Length, nameof(message));
|
||||
AllowedEntities.Unknown9
|
||||
| // 200
|
||||
AllowedEntities.Payloads
|
||||
| // 40
|
||||
AllowedEntities.OtherCharacters
|
||||
| // 20
|
||||
AllowedEntities.CharacterList
|
||||
| // 10
|
||||
AllowedEntities.SpecialCharacters
|
||||
| // 8
|
||||
AllowedEntities.Numbers
|
||||
| // 4
|
||||
AllowedEntities.LowercaseLetters
|
||||
| // 2
|
||||
AllowedEntities.UppercaseLetters, // 1
|
||||
null
|
||||
);
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(
|
||||
unsanitizedLength,
|
||||
str->Length,
|
||||
nameof(message)
|
||||
);
|
||||
|
||||
UIModule.Instance()->ProcessChatBoxEntry(str);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Colors;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -15,7 +15,8 @@ public static class Colors
|
||||
public static readonly Vector4 Collectability = new(0.99f, 0.56f, 0.57f, 1f);
|
||||
public static readonly Vector4 CP = new(0.63f, 0.37f, 0.75f, 1f);
|
||||
|
||||
private static Vector4 SolverProgressBg => ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.TableBorderLight));
|
||||
private static Vector4 SolverProgressBg =>
|
||||
ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.TableBorderLight));
|
||||
private static Vector4 SolverProgressFgBland => ImGuiColors.DalamudWhite2;
|
||||
|
||||
private static readonly Vector4[] SolverProgressFgColorful =
|
||||
@@ -52,7 +53,7 @@ public static class Colors
|
||||
{
|
||||
Configuration.ProgressBarType.Colorful => SolverProgressFgColorful,
|
||||
Configuration.ProgressBarType.Simple => SolverProgressFgMonochromatic,
|
||||
_ => throw new InvalidOperationException("No progress bar should be visible")
|
||||
_ => throw new InvalidOperationException("No progress bar should be visible"),
|
||||
};
|
||||
|
||||
if (stageValue is not { } stage)
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
using Dalamud.Networking.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Solver;
|
||||
using Dalamud.Networking.Http;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -40,7 +40,9 @@ public sealed class CommunityMacros
|
||||
{
|
||||
[JsonPropertyName("integerValue")]
|
||||
[JsonRequired]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonNumberHandling(
|
||||
JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString
|
||||
)]
|
||||
public required int Value { get; set; }
|
||||
|
||||
public static implicit operator int(IntegerValue v) => v.Value;
|
||||
@@ -59,6 +61,7 @@ public sealed class CommunityMacros
|
||||
public required ValueData Data { get; set; }
|
||||
|
||||
public T Value => Data.Fields;
|
||||
|
||||
public static implicit operator T(MapValue<T> v) => v.Value;
|
||||
}
|
||||
|
||||
@@ -74,6 +77,7 @@ public sealed class CommunityMacros
|
||||
public required ValueData Data { get; set; }
|
||||
|
||||
public T[] Value => Data.Values ?? [];
|
||||
|
||||
public static implicit operator T[](ArrayValue<T> v) => v.Value;
|
||||
}
|
||||
|
||||
@@ -88,8 +92,10 @@ public sealed class CommunityMacros
|
||||
{
|
||||
[JsonRequired]
|
||||
public required List<CollectionSelector> From { get; set; }
|
||||
|
||||
[JsonRequired]
|
||||
public required Filter Where { get; set; }
|
||||
|
||||
[JsonRequired]
|
||||
public required List<Order> OrderBy { get; set; }
|
||||
}
|
||||
@@ -109,6 +115,7 @@ public sealed class CommunityMacros
|
||||
{
|
||||
[JsonRequired]
|
||||
public required List<Filter> Filters { get; set; }
|
||||
|
||||
[JsonRequired]
|
||||
public required CompositeOperator Op { get; set; }
|
||||
}
|
||||
@@ -117,13 +124,14 @@ public sealed class CommunityMacros
|
||||
{
|
||||
OPERATOR_UNSPECIFIED,
|
||||
AND,
|
||||
OR
|
||||
OR,
|
||||
}
|
||||
|
||||
public sealed record FieldFilter
|
||||
{
|
||||
[JsonRequired]
|
||||
public required FieldReference Field { get; set; }
|
||||
|
||||
[JsonRequired]
|
||||
public required FieldOperator Op { get; set; }
|
||||
public object? Value { get; set; }
|
||||
@@ -141,13 +149,14 @@ public sealed class CommunityMacros
|
||||
ARRAY_CONTAINS,
|
||||
IN,
|
||||
ARRAY_CONTAINS_ANY,
|
||||
NOT_IN
|
||||
NOT_IN,
|
||||
}
|
||||
|
||||
public sealed record Order
|
||||
{
|
||||
[JsonRequired]
|
||||
public required FieldReference Field { get; set; }
|
||||
|
||||
[JsonRequired]
|
||||
public required Direction Direction { get; set; }
|
||||
}
|
||||
@@ -162,7 +171,7 @@ public sealed class CommunityMacros
|
||||
{
|
||||
DIRECTION_UNSPECIFIED,
|
||||
ASCENDING,
|
||||
DESCENDING
|
||||
DESCENDING,
|
||||
}
|
||||
|
||||
private sealed record RunQueryRequest
|
||||
@@ -178,6 +187,7 @@ public sealed class CommunityMacros
|
||||
[JsonRequired]
|
||||
[JsonPropertyName("rlvl")]
|
||||
public required IntegerValue RLvl { get; set; }
|
||||
|
||||
[JsonRequired]
|
||||
public required IntegerValue Durability { get; set; }
|
||||
}
|
||||
@@ -185,6 +195,7 @@ public sealed class CommunityMacros
|
||||
public sealed record FieldData
|
||||
{
|
||||
public StringValue? Name { get; set; }
|
||||
|
||||
[JsonRequired]
|
||||
public required ArrayValue<StringValue> Rotation { get; set; }
|
||||
public MapValue<RecipeFieldData>? Recipe { get; set; }
|
||||
@@ -212,6 +223,7 @@ public sealed class CommunityMacros
|
||||
public string? Slug { get; set; }
|
||||
public string? Version { get; set; }
|
||||
public string? Job { get; set; }
|
||||
|
||||
[JsonPropertyName("job_level")]
|
||||
public int JobLevel { get; set; }
|
||||
public int Craftsmanship { get; set; }
|
||||
@@ -219,11 +231,14 @@ public sealed class CommunityMacros
|
||||
public int CP { get; set; }
|
||||
public string? Food { get; set; }
|
||||
public string? Potion { get; set; }
|
||||
|
||||
[JsonPropertyName("recipe_job_level")]
|
||||
public int RecipeJobLevel { get; set; }
|
||||
public string? Recipe { get; set; }
|
||||
|
||||
// HqIngredients
|
||||
public string? Actions { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public long CreatedAt { get; set; }
|
||||
public string? Error { get; set; }
|
||||
@@ -241,10 +256,13 @@ public sealed class CommunityMacros
|
||||
throw new Exception($"Internal error; No fields were returned");
|
||||
|
||||
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/67f453041c6b2b31d32fcf6e1fd53aa38ed7a12b/apps/client/src/app/model/other/crafting-rotation.ts#L49
|
||||
Name = rotation.Name?.Value ??
|
||||
(rotation.Recipe is { Value: var recipe } ?
|
||||
$"rlvl{recipe.RLvl.Value} - {rotation.Rotation.Value.Length} steps, {recipe.Durability.Value} dur" :
|
||||
"New Teamcraft Rotation");
|
||||
Name =
|
||||
rotation.Name?.Value
|
||||
?? (
|
||||
rotation.Recipe is { Value: var recipe }
|
||||
? $"rlvl{recipe.RLvl.Value} - {rotation.Rotation.Value.Length} steps, {recipe.Durability.Value} dur"
|
||||
: "New Teamcraft Rotation"
|
||||
);
|
||||
|
||||
var actions = new List<ActionType>();
|
||||
foreach (var action in rotation.Rotation.Value)
|
||||
@@ -290,7 +308,7 @@ public sealed class CommunityMacros
|
||||
|
||||
"RemoveFinalAppraisal" => null,
|
||||
// Old actions?
|
||||
_ => null
|
||||
_ => null,
|
||||
};
|
||||
if (actionType.HasValue)
|
||||
actions.Add(actionType.Value);
|
||||
@@ -353,7 +371,7 @@ public sealed class CommunityMacros
|
||||
"DelicateSynthesisTraited" => ActionType.DelicateSynthesis,
|
||||
|
||||
// Old actions?
|
||||
_ => null
|
||||
_ => null,
|
||||
};
|
||||
if (actionType.HasValue)
|
||||
actions.Add(actionType.Value);
|
||||
@@ -361,16 +379,30 @@ public sealed class CommunityMacros
|
||||
Actions = actions;
|
||||
}
|
||||
|
||||
public (float Score, SimulationState FinalState) CalculateScore(SimulatorNoRandom simulator, in SimulationState startingState, in MCTSConfig mctsConfig)
|
||||
public (float Score, SimulationState FinalState) CalculateScore(
|
||||
SimulatorNoRandom simulator,
|
||||
in SimulationState startingState,
|
||||
in MCTSConfig mctsConfig
|
||||
)
|
||||
{
|
||||
return CalculateScore(Actions, simulator, startingState, mctsConfig);
|
||||
}
|
||||
|
||||
public static (float Score, SimulationState FinalState) CalculateScore(IReadOnlyCollection<ActionType> actions, SimulatorNoRandom simulator, in SimulationState startingState, in MCTSConfig mctsConfig)
|
||||
public static (float Score, SimulationState FinalState) CalculateScore(
|
||||
IReadOnlyCollection<ActionType> actions,
|
||||
SimulatorNoRandom simulator,
|
||||
in SimulationState startingState,
|
||||
in MCTSConfig mctsConfig
|
||||
)
|
||||
{
|
||||
var (resp, outState, failedIdx) = simulator.ExecuteMultiple(startingState, actions);
|
||||
outState.ActionCount = actions.Count;
|
||||
var score = SimulationNode.CalculateScoreForState(outState, simulator.CompletionState, mctsConfig) ?? 0;
|
||||
var score =
|
||||
SimulationNode.CalculateScoreForState(
|
||||
outState,
|
||||
simulator.CompletionState,
|
||||
mctsConfig
|
||||
) ?? 0;
|
||||
if (resp != ActionResponse.SimulationComplete)
|
||||
{
|
||||
if (failedIdx != -1)
|
||||
@@ -382,7 +414,10 @@ public sealed class CommunityMacros
|
||||
|
||||
private Dictionary<int, List<CommunityMacro>> CachedRotations { get; } = [];
|
||||
|
||||
public async Task<IReadOnlyList<CommunityMacro>> RetrieveRotations(int rlvl, CancellationToken token)
|
||||
public async Task<IReadOnlyList<CommunityMacro>> RetrieveRotations(
|
||||
int rlvl,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
lock (CachedRotations)
|
||||
{
|
||||
@@ -397,23 +432,25 @@ public sealed class CommunityMacros
|
||||
return macros;
|
||||
}
|
||||
|
||||
private static async Task<List<TeamcraftMacro>> RetrieveRotationsInternal(int rlvl, CancellationToken token)
|
||||
private static async Task<List<TeamcraftMacro>> RetrieveRotationsInternal(
|
||||
int rlvl,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
using var heCallback = new HappyEyeballsCallback();
|
||||
using var client = new HttpClient(new SocketsHttpHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
ConnectCallback = heCallback.ConnectCallback,
|
||||
});
|
||||
using var client = new HttpClient(
|
||||
new SocketsHttpHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
ConnectCallback = heCallback.ConnectCallback,
|
||||
}
|
||||
);
|
||||
|
||||
var request = new RunQueryRequest
|
||||
{
|
||||
StructuredQuery = new StructuredQuery
|
||||
{
|
||||
From =
|
||||
[
|
||||
new() { CollectionId = "rotations" }
|
||||
],
|
||||
From = [new() { CollectionId = "rotations" }],
|
||||
Where = new Filter
|
||||
{
|
||||
CompositeFilter = new CompositeFilter
|
||||
@@ -427,8 +464,8 @@ public sealed class CommunityMacros
|
||||
{
|
||||
Field = new FieldReference { FieldPath = "public" },
|
||||
Op = FieldOperator.EQUAL,
|
||||
Value = new BooleanValue { Value = true }
|
||||
}
|
||||
Value = new BooleanValue { Value = true },
|
||||
},
|
||||
},
|
||||
new()
|
||||
{
|
||||
@@ -436,10 +473,10 @@ public sealed class CommunityMacros
|
||||
{
|
||||
Field = new FieldReference { FieldPath = "community.rlvl" },
|
||||
Op = FieldOperator.EQUAL,
|
||||
Value = new IntegerValue { Value = rlvl }
|
||||
}
|
||||
}
|
||||
]
|
||||
Value = new IntegerValue { Value = rlvl },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
OrderBy =
|
||||
@@ -447,39 +484,50 @@ public sealed class CommunityMacros
|
||||
new()
|
||||
{
|
||||
Field = new FieldReference { FieldPath = "xivVersion" },
|
||||
Direction = Direction.DESCENDING
|
||||
Direction = Direction.DESCENDING,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Field = new FieldReference { FieldPath = "__name__" },
|
||||
Direction = Direction.DESCENDING
|
||||
}
|
||||
]
|
||||
Direction = Direction.DESCENDING,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
var resp = await PostFromJsonAsync<RunQueryRequest, List<QueriedTeamcraftMacro>>(
|
||||
client,
|
||||
$"https://firestore.googleapis.com/v1beta1/projects/ffxivteamcraft/databases/(default)/documents:runQuery",
|
||||
request, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
}, token).
|
||||
ConfigureAwait(false);
|
||||
client,
|
||||
$"https://firestore.googleapis.com/v1beta1/projects/ffxivteamcraft/databases/(default)/documents:runQuery",
|
||||
request,
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
},
|
||||
token
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
if (resp is null)
|
||||
throw new Exception("Internal server error; failed to retrieve macro");
|
||||
|
||||
foreach(var macro in resp)
|
||||
foreach (var macro in resp)
|
||||
{
|
||||
if (macro.Error is { } error)
|
||||
throw new Exception($"Internal server error ({error.Status}); {error.Message}");
|
||||
}
|
||||
|
||||
return resp.Where(macro => macro.Document is not null).Select(macro => macro.Document!).ToList();
|
||||
return resp.Where(macro => macro.Document is not null)
|
||||
.Select(macro => macro.Document!)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static async Task<TResponse?> PostFromJsonAsync<TRequest, TResponse>(HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, TRequest value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
|
||||
private static async Task<TResponse?> PostFromJsonAsync<TRequest, TResponse>(
|
||||
HttpClient client,
|
||||
[StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri,
|
||||
TRequest value,
|
||||
JsonSerializerOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(client);
|
||||
|
||||
@@ -487,6 +535,8 @@ public sealed class CommunityMacros
|
||||
using var message = await resp.ConfigureAwait(false);
|
||||
message.EnsureSuccessStatusCode();
|
||||
|
||||
return await message.Content!.ReadFromJsonAsync<TResponse>(options, cancellationToken).ConfigureAwait(false);
|
||||
return await message
|
||||
.Content!.ReadFromJsonAsync<TResponse>(options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ internal static class CraftimizerConflictDetector
|
||||
|
||||
throw new System.InvalidOperationException(
|
||||
"Forgeimizer cannot load while the upstream Craftimizer plugin is active.\n\n"
|
||||
+ "Both plugins register the same Dalamud hooks (UseAction, IsActionHighlighted) "
|
||||
+ "and would conflict if loaded together.\n\n"
|
||||
+ "Action: open /xlplugins, disable the upstream 'Craftimizer' plugin, "
|
||||
+ "then re-enable Forgeimizer."
|
||||
+ "Both plugins register the same Dalamud hooks (UseAction, IsActionHighlighted) "
|
||||
+ "and would conflict if loaded together.\n\n"
|
||||
+ "Action: open /xlplugins, disable the upstream 'Craftimizer' plugin, "
|
||||
+ "then re-enable Forgeimizer."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Utility.Numerics;
|
||||
using System.Numerics;
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Utility.Numerics;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
internal static class DynamicBars
|
||||
{
|
||||
public readonly record struct BarData(string Name, Vector4 Color, SimulatedMacro.Reliablity.Param? Reliability, float Value, float Max, IReadOnlyList<int?>? Collectability = null, string? Caption = null, string? DefaultCaptionSizeText = null, Action<DrawerParams>? CustomDrawer = null)
|
||||
public readonly record struct BarData(
|
||||
string Name,
|
||||
Vector4 Color,
|
||||
SimulatedMacro.Reliablity.Param? Reliability,
|
||||
float Value,
|
||||
float Max,
|
||||
IReadOnlyList<int?>? Collectability = null,
|
||||
string? Caption = null,
|
||||
string? DefaultCaptionSizeText = null,
|
||||
Action<DrawerParams>? CustomDrawer = null
|
||||
)
|
||||
{
|
||||
public BarData(string name, Action<DrawerParams> customDrawer) : this(name, default, null, 0, 0, null, null, null, customDrawer)
|
||||
{
|
||||
public BarData(string name, Action<DrawerParams> customDrawer)
|
||||
: this(name, default, null, 0, 0, null, null, null, customDrawer) { }
|
||||
|
||||
}
|
||||
|
||||
public BarData(string name, Vector4 color, float value, float max) : this(name, color, null, value, max, null, null, null)
|
||||
{
|
||||
|
||||
}
|
||||
public BarData(string name, Vector4 color, float value, float max)
|
||||
: this(name, color, null, value, max, null, null, null) { }
|
||||
}
|
||||
|
||||
public readonly record struct DrawerParams(float TotalSize, float Spacing);
|
||||
@@ -39,13 +45,19 @@ internal static class DynamicBars
|
||||
return Math.Max(ImGui.CalcTextSize(caption).X, defaultSize);
|
||||
// max (sp/2) "/" (sp/2) max
|
||||
return Math.Max(
|
||||
Math.Max(ImGui.CalcTextSize($"{b.Value:0}").X, ImGui.CalcTextSize($"{b.Max:0}").X) * 2
|
||||
+ ImGui.GetStyle().ItemSpacing.X
|
||||
+ ImGui.CalcTextSize("/").X,
|
||||
defaultSize);
|
||||
Math.Max(ImGui.CalcTextSize($"{b.Value:0}").X, ImGui.CalcTextSize($"{b.Max:0}").X)
|
||||
* 2
|
||||
+ ImGui.GetStyle().ItemSpacing.X
|
||||
+ ImGui.CalcTextSize("/").X,
|
||||
defaultSize
|
||||
);
|
||||
});
|
||||
|
||||
private static ImRaii.ColorDisposable? PushCollectableColor(this in BarData bar, float collectability, bool colorUnmetThreshold = true)
|
||||
private static ImRaii.ColorDisposable? PushCollectableColor(
|
||||
this in BarData bar,
|
||||
float collectability,
|
||||
bool colorUnmetThreshold = true
|
||||
)
|
||||
{
|
||||
if (bar.Collectability is not { } collectabilities)
|
||||
return null;
|
||||
@@ -88,7 +100,10 @@ internal static class DynamicBars
|
||||
var pos = ImGui.GetCursorPos();
|
||||
var screenPos = ImGui.GetCursorScreenPos();
|
||||
using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color))
|
||||
ImGuiUtils.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()));
|
||||
ImGuiUtils.ProgressBar(
|
||||
Math.Clamp(bar.Value / bar.Max, 0, 1),
|
||||
new(barSize, ImGui.GetFrameHeight())
|
||||
);
|
||||
if (bar.Collectability is { } collectability)
|
||||
{
|
||||
var i = 0;
|
||||
@@ -101,27 +116,36 @@ internal static class DynamicBars
|
||||
continue;
|
||||
var offset = barSize * threshold / bar.Max;
|
||||
var isLast = i == collectability.Count;
|
||||
var offsetNext = isLast ? barSize : barSize * collectability[i]!.Value / bar.Max;
|
||||
var offsetNext = isLast
|
||||
? barSize
|
||||
: barSize * collectability[i]!.Value / bar.Max;
|
||||
var passedThreshold = bar.Value >= threshold;
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
screenPos + new Vector2(offset, 0),
|
||||
screenPos + new Vector2(offsetNext, height),
|
||||
ImGui.GetColorU32(color.WithW(passedThreshold ? 0.6f : 0.2f)),
|
||||
isLast ? rounding : 0
|
||||
);
|
||||
ImGui.GetWindowDrawList().AddLine(
|
||||
screenPos + new Vector2(offset, 0),
|
||||
screenPos + new Vector2(offset, height),
|
||||
ImGui.GetColorU32(color),
|
||||
Math.Max(passedThreshold ? 3 : 1.5f, rounding / 2f)
|
||||
);
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRectFilled(
|
||||
screenPos + new Vector2(offset, 0),
|
||||
screenPos + new Vector2(offsetNext, height),
|
||||
ImGui.GetColorU32(color.WithW(passedThreshold ? 0.6f : 0.2f)),
|
||||
isLast ? rounding : 0
|
||||
);
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddLine(
|
||||
screenPos + new Vector2(offset, 0),
|
||||
screenPos + new Vector2(offset, height),
|
||||
ImGui.GetColorU32(color),
|
||||
Math.Max(passedThreshold ? 3 : 1.5f, rounding / 2f)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenOverlapped))
|
||||
{
|
||||
if (bar.Reliability is { } reliability)
|
||||
{
|
||||
if (reliability.GetViolinData(bar.Max, (int)(barSize / 5), 0.02) is { } violinData)
|
||||
if (
|
||||
reliability.GetViolinData(bar.Max, (int)(barSize / 5), 0.02) is
|
||||
{ } violinData
|
||||
)
|
||||
{
|
||||
ImGui.SetCursorPos(pos);
|
||||
ImGuiUtils.ViolinPlot(violinData, new(barSize, ImGui.GetFrameHeight()));
|
||||
@@ -129,7 +153,10 @@ internal static class DynamicBars
|
||||
{
|
||||
using var _font = ImRaii.PushFont(UiBuilder.DefaultFont);
|
||||
using var _tooltip = ImRaii.Tooltip();
|
||||
using var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
using var _ = ImRaii.PushStyle(
|
||||
ImGuiStyleVar.ItemSpacing,
|
||||
Vector2.Zero
|
||||
);
|
||||
|
||||
ImGui.TextUnformatted("Min: ");
|
||||
ImGui.SameLine(0, 0);
|
||||
@@ -221,7 +248,8 @@ internal static class DynamicBars
|
||||
{
|
||||
tooltip = $"Solver Progress: {solver.ProgressValue:N0} / {solver.ProgressMax:N0}";
|
||||
if (solver.ProgressValue > solver.ProgressMax)
|
||||
tooltip += $"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate.";
|
||||
tooltip +=
|
||||
$"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate.";
|
||||
}
|
||||
ImGuiUtils.TooltipWrapped(tooltip);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Lumina.Excel.Sheets;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Craftimizer.Plugin;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -15,8 +15,20 @@ public static class FoodStatus
|
||||
private static readonly ImmutableArray<uint> FoodOrder;
|
||||
private static readonly ImmutableArray<uint> MedicineOrder;
|
||||
|
||||
public readonly record struct FoodStat(bool IsRelative, int Value, int Max, int ValueHQ, int MaxHQ);
|
||||
public readonly record struct Food(Item Item, FoodStat? Craftsmanship, FoodStat? Control, FoodStat? CP);
|
||||
public readonly record struct FoodStat(
|
||||
bool IsRelative,
|
||||
int Value,
|
||||
int Max,
|
||||
int ValueHQ,
|
||||
int MaxHQ
|
||||
);
|
||||
|
||||
public readonly record struct Food(
|
||||
Item Item,
|
||||
FoodStat? Craftsmanship,
|
||||
FoodStat? Control,
|
||||
FoodStat? CP
|
||||
);
|
||||
|
||||
static FoodStatus()
|
||||
{
|
||||
@@ -39,18 +51,33 @@ public static class FoodStatus
|
||||
if (LuminaSheets.ItemFoodSheet.GetRowOrDefault(itemAction.Data[1]) is not { } itemFood)
|
||||
continue;
|
||||
|
||||
FoodStat? craftsmanship = null, control = null, cp = null;
|
||||
FoodStat? craftsmanship = null,
|
||||
control = null,
|
||||
cp = null;
|
||||
foreach (var stat in itemFood.Params)
|
||||
{
|
||||
if (stat.BaseParam.RowId == 0)
|
||||
continue;
|
||||
var foodStat = new FoodStat(stat.IsRelative, stat.Value, stat.Max, stat.ValueHQ, stat.MaxHQ);
|
||||
var foodStat = new FoodStat(
|
||||
stat.IsRelative,
|
||||
stat.Value,
|
||||
stat.Max,
|
||||
stat.ValueHQ,
|
||||
stat.MaxHQ
|
||||
);
|
||||
switch (stat.BaseParam.RowId)
|
||||
{
|
||||
case Gearsets.ParamCraftsmanship: craftsmanship = foodStat; break;
|
||||
case Gearsets.ParamControl: control = foodStat; break;
|
||||
case Gearsets.ParamCP: cp = foodStat; break;
|
||||
default: continue;
|
||||
case Gearsets.ParamCraftsmanship:
|
||||
craftsmanship = foodStat;
|
||||
break;
|
||||
case Gearsets.ParamControl:
|
||||
control = foodStat;
|
||||
break;
|
||||
case Gearsets.ParamCP:
|
||||
cp = foodStat;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +97,23 @@ public static class FoodStatus
|
||||
FoodItems = foods.ToFrozenDictionary();
|
||||
MedicineItems = medicines.ToFrozenDictionary();
|
||||
|
||||
FoodOrder = [.. FoodItems.OrderByDescending(a => a.Value.Item.LevelItem.RowId).Select(a => a.Key)];
|
||||
MedicineOrder = [.. MedicineItems.OrderByDescending(a => a.Value.Item.LevelItem.RowId).Select(a => a.Key)];
|
||||
FoodOrder =
|
||||
[
|
||||
.. FoodItems.OrderByDescending(a => a.Value.Item.LevelItem.RowId).Select(a => a.Key),
|
||||
];
|
||||
MedicineOrder =
|
||||
[
|
||||
.. MedicineItems
|
||||
.OrderByDescending(a => a.Value.Item.LevelItem.RowId)
|
||||
.Select(a => a.Key),
|
||||
];
|
||||
}
|
||||
|
||||
public static void Initialize() { }
|
||||
|
||||
public static IEnumerable<Food> OrderedFoods => FoodOrder.Select(id => FoodItems[id]);
|
||||
public static IEnumerable<Food> OrderedMedicines => MedicineOrder.Select(id => MedicineItems[id]);
|
||||
public static IEnumerable<Food> OrderedMedicines =>
|
||||
MedicineOrder.Select(id => MedicineItems[id]);
|
||||
|
||||
public static (uint ItemId, bool IsHQ)? ResolveFoodParam(ushort param)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,11 @@ internal readonly struct FuzzyMatcher
|
||||
{
|
||||
MatchMode.FuzzyParts => FindNeedleSegments(needleString),
|
||||
MatchMode.Fuzzy or MatchMode.Simple => EmptySegArray,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(matchMode), matchMode, "Invalid match mode"),
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(matchMode),
|
||||
matchMode,
|
||||
"Invalid match mode"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,7 +63,9 @@ internal readonly struct FuzzyMatcher
|
||||
return 0;
|
||||
|
||||
if (mode == MatchMode.Simple)
|
||||
return value.Contains(needleString, StringComparison.InvariantCultureIgnoreCase) ? 1 : 0;
|
||||
return value.Contains(needleString, StringComparison.InvariantCultureIgnoreCase)
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
if (mode == MatchMode.Fuzzy)
|
||||
return GetRawScore(value, 0, needleFinalPosition);
|
||||
@@ -101,7 +107,11 @@ internal readonly struct FuzzyMatcher
|
||||
|
||||
private int GetRawScore(ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
|
||||
{
|
||||
var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(haystack, needleStart, needleEnd);
|
||||
var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(
|
||||
haystack,
|
||||
needleStart,
|
||||
needleEnd
|
||||
);
|
||||
if (startPos < 0)
|
||||
return 0;
|
||||
|
||||
@@ -109,28 +119,40 @@ internal readonly struct FuzzyMatcher
|
||||
|
||||
var score = CalculateRawScore(needleSize, startPos, gaps, consecutive, borderMatches);
|
||||
|
||||
(startPos, gaps, consecutive, borderMatches) = FindReverse(haystack, endPos, needleStart, needleEnd);
|
||||
(startPos, gaps, consecutive, borderMatches) = FindReverse(
|
||||
haystack,
|
||||
endPos,
|
||||
needleStart,
|
||||
needleEnd
|
||||
);
|
||||
var revScore = CalculateRawScore(needleSize, startPos, gaps, consecutive, borderMatches);
|
||||
|
||||
return int.Max(score, revScore);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CalculateRawScore(int needleSize, int startPos, int gaps, int consecutive, int borderMatches)
|
||||
private static int CalculateRawScore(
|
||||
int needleSize,
|
||||
int startPos,
|
||||
int gaps,
|
||||
int consecutive,
|
||||
int borderMatches
|
||||
)
|
||||
{
|
||||
var score = 100
|
||||
+ needleSize * 3
|
||||
+ borderMatches * 3
|
||||
+ consecutive * 5
|
||||
- startPos
|
||||
- gaps * 2;
|
||||
var score =
|
||||
100 + needleSize * 3 + borderMatches * 3 + consecutive * 5 - startPos - gaps * 2;
|
||||
if (startPos == 0)
|
||||
score += 5;
|
||||
return score < 1 ? 1 : score;
|
||||
}
|
||||
|
||||
private (int StartPos, int Gaps, int Consecutive, int BorderMatches, int HaystackIndex) FindForward(
|
||||
ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
|
||||
private (
|
||||
int StartPos,
|
||||
int Gaps,
|
||||
int Consecutive,
|
||||
int BorderMatches,
|
||||
int HaystackIndex
|
||||
) FindForward(ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
|
||||
{
|
||||
var needleIndex = needleStart;
|
||||
var lastMatchIndex = -10;
|
||||
@@ -176,7 +198,11 @@ internal readonly struct FuzzyMatcher
|
||||
}
|
||||
|
||||
private (int StartPos, int Gaps, int Consecutive, int BorderMatches) FindReverse(
|
||||
ReadOnlySpan<char> haystack, int haystackLastMatchIndex, int needleStart, int needleEnd)
|
||||
ReadOnlySpan<char> haystack,
|
||||
int haystackLastMatchIndex,
|
||||
int needleStart,
|
||||
int needleEnd
|
||||
)
|
||||
{
|
||||
var needleIndex = needleEnd;
|
||||
var revLastMatchIndex = haystack.Length + 10;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using Lumina.Excel.Sheets;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Craftimizer.Plugin;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
public static unsafe class Gearsets
|
||||
{
|
||||
public record struct GearsetStats(int CP, int Craftsmanship, int Control);
|
||||
|
||||
public record struct GearsetMateria(ushort Type, ushort Grade);
|
||||
|
||||
public record struct GearsetItem(uint ItemId, bool IsHq, GearsetMateria[] Materia);
|
||||
|
||||
private static readonly GearsetStats BaseStats = new(180, 0, 0);
|
||||
@@ -27,7 +29,11 @@ public static unsafe class Gearsets
|
||||
for (var i = 0; i < container->Size; ++i)
|
||||
{
|
||||
var item = container->Items[i];
|
||||
items[i] = new(item.ItemId, item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality), GetMaterias(item.Materia, item.MateriaGrades));
|
||||
items[i] = new(
|
||||
item.ItemId,
|
||||
item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality),
|
||||
GetMaterias(item.Materia, item.MateriaGrades)
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@@ -39,7 +45,11 @@ public static unsafe class Gearsets
|
||||
for (var i = 0; i < 14; ++i)
|
||||
{
|
||||
var item = gearsetItems[i];
|
||||
items[i] = new(item.ItemId % 1000000, item.ItemId > 1000000, GetMaterias(item.Materia, item.MateriaGrades));
|
||||
items[i] = new(
|
||||
item.ItemId % 1000000,
|
||||
item.ItemId > 1000000,
|
||||
GetMaterias(item.Materia, item.MateriaGrades)
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@@ -48,7 +58,9 @@ public static unsafe class Gearsets
|
||||
{
|
||||
var item = LuminaSheets.ItemSheet.GetRow(gearsetItem.ItemId)!;
|
||||
|
||||
int cp = 0, craftsmanship = 0, control = 0;
|
||||
int cp = 0,
|
||||
craftsmanship = 0,
|
||||
control = 0;
|
||||
|
||||
void IncreaseStat(uint baseParam, int amount)
|
||||
{
|
||||
@@ -83,7 +95,12 @@ public static unsafe class Gearsets
|
||||
}
|
||||
|
||||
public static GearsetStats CalculateGearsetStats(GearsetItem[] gearsetItems) =>
|
||||
gearsetItems.Select(CalculateGearsetItemStats).Aggregate(BaseStats, (a, b) => new(a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control));
|
||||
gearsetItems
|
||||
.Select(CalculateGearsetItemStats)
|
||||
.Aggregate(
|
||||
BaseStats,
|
||||
(a, b) => new(a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control)
|
||||
);
|
||||
|
||||
public static GearsetStats CalculateGearsetCurrentStats()
|
||||
{
|
||||
@@ -97,10 +114,24 @@ public static unsafe class Gearsets
|
||||
};
|
||||
}
|
||||
|
||||
public static CharacterStats CalculateCharacterStats(GearsetItem[] gearsetItems, int characterLevel, bool canUseManipulation) =>
|
||||
CalculateCharacterStats(CalculateGearsetStats(gearsetItems), gearsetItems, characterLevel, canUseManipulation);
|
||||
public static CharacterStats CalculateCharacterStats(
|
||||
GearsetItem[] gearsetItems,
|
||||
int characterLevel,
|
||||
bool canUseManipulation
|
||||
) =>
|
||||
CalculateCharacterStats(
|
||||
CalculateGearsetStats(gearsetItems),
|
||||
gearsetItems,
|
||||
characterLevel,
|
||||
canUseManipulation
|
||||
);
|
||||
|
||||
public static CharacterStats CalculateCharacterStats(GearsetStats gearsetStats, GearsetItem[] gearsetItems, int characterLevel, bool canUseManipulation) =>
|
||||
public static CharacterStats CalculateCharacterStats(
|
||||
GearsetStats gearsetStats,
|
||||
GearsetItem[] gearsetItems,
|
||||
int characterLevel,
|
||||
bool canUseManipulation
|
||||
) =>
|
||||
new()
|
||||
{
|
||||
CP = gearsetStats.CP,
|
||||
@@ -115,8 +146,7 @@ public static unsafe class Gearsets
|
||||
public static bool HasDelineations() =>
|
||||
InventoryManager.Instance()->GetInventoryItemCount(28724) > 0;
|
||||
|
||||
public static bool IsItem(GearsetItem item, uint itemId) =>
|
||||
item.ItemId == itemId;
|
||||
public static bool IsItem(GearsetItem item, uint itemId) => item.ItemId == itemId;
|
||||
|
||||
public static bool IsSpecialistSoulCrystal(GearsetItem item)
|
||||
{
|
||||
@@ -125,11 +155,18 @@ public static unsafe class Gearsets
|
||||
|
||||
var luminaItem = LuminaSheets.ItemSheet.GetRow(item.ItemId)!;
|
||||
// Soul Crystal ItemUICategory DoH Category
|
||||
return luminaItem.ItemUICategory.RowId == 62 && luminaItem.ClassJobUse.Value.ClassJobCategory.RowId == 33;
|
||||
return luminaItem.ItemUICategory.RowId == 62
|
||||
&& luminaItem.ClassJobUse.Value.ClassJobCategory.RowId == 33;
|
||||
}
|
||||
|
||||
public static bool IsSplendorousTool(GearsetItem item) =>
|
||||
LuminaSheets.ItemSheetEnglish.GetRow(item.ItemId).Description.ToString().Contains("Increases to quality are 1.75 times higher than normal when material condition is Good.", StringComparison.Ordinal);
|
||||
LuminaSheets
|
||||
.ItemSheetEnglish.GetRow(item.ItemId)
|
||||
.Description.ToString()
|
||||
.Contains(
|
||||
"Increases to quality are 1.75 times higher than normal when material condition is Good.",
|
||||
StringComparison.Ordinal
|
||||
);
|
||||
|
||||
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/client/src/app/modules/gearsets/materia.service.ts#L265
|
||||
private static int CalculateParamCap(Item item, uint paramId)
|
||||
@@ -142,14 +179,14 @@ public static unsafe class Gearsets
|
||||
ParamCP => ilvl.CP,
|
||||
ParamCraftsmanship => ilvl.Craftsmanship,
|
||||
ParamControl => ilvl.Control,
|
||||
_ => 0
|
||||
_ => 0,
|
||||
};
|
||||
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/data-extraction/src/extractors/items.extractor.ts#L6
|
||||
var slotMod = item.EquipSlotCategory.RowId switch
|
||||
{
|
||||
1 => param.OneHandWeaponPercent, // column 4
|
||||
2 => param.OffHandPercent, // column 5
|
||||
3 => param.HeadPercent, // ...
|
||||
2 => param.OffHandPercent, // column 5
|
||||
3 => param.HeadPercent, // ...
|
||||
4 => param.ChestPercent,
|
||||
5 => param.HandsPercent,
|
||||
6 => param.WaistPercent,
|
||||
@@ -168,16 +205,20 @@ public static unsafe class Gearsets
|
||||
19 => param.HeadChestHandsLegsFeetPercent,
|
||||
20 => param.ChestLegsGlovesPercent,
|
||||
21 => param.ChestLegsFeetPercent,
|
||||
_ => 0
|
||||
_ => 0,
|
||||
};
|
||||
var roleMod = param.MeldParam[item.BaseParamModifier];
|
||||
|
||||
// https://github.com/Caraxi/SimpleTweaksPlugin/pull/595
|
||||
var cap = (int)Math.Round((float)baseValue * slotMod / (roleMod * 10f), MidpointRounding.AwayFromZero);
|
||||
var cap = (int)
|
||||
Math.Round((float)baseValue * slotMod / (roleMod * 10f), MidpointRounding.AwayFromZero);
|
||||
return cap == 0 ? int.MaxValue : cap;
|
||||
}
|
||||
|
||||
private static GearsetMateria[] GetMaterias(ReadOnlySpan<ushort> types, ReadOnlySpan<byte> grades)
|
||||
private static GearsetMateria[] GetMaterias(
|
||||
ReadOnlySpan<ushort> types,
|
||||
ReadOnlySpan<byte> grades
|
||||
)
|
||||
{
|
||||
var materia = new GearsetMateria[5];
|
||||
for (var i = 0; i < 5; ++i)
|
||||
|
||||
+63
-11
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using System;
|
||||
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
||||
using ActionUtils = Craftimizer.Plugin.ActionUtils;
|
||||
using CSActionType = FFXIVClientStructs.FFXIV.Client.Game.ActionType;
|
||||
@@ -14,33 +14,77 @@ public sealed unsafe class Hooks : IDisposable
|
||||
|
||||
public event OnActionUsedDelegate? OnActionUsed;
|
||||
|
||||
public delegate bool UseActionDelegate(ActionManager* manager, CSActionType actionType, uint actionId, ulong targetId, uint extraParam, ActionManager.UseActionMode mode, uint comboRouteId, bool* outOptAreaTargeted);
|
||||
public delegate bool UseActionDelegate(
|
||||
ActionManager* manager,
|
||||
CSActionType actionType,
|
||||
uint actionId,
|
||||
ulong targetId,
|
||||
uint extraParam,
|
||||
ActionManager.UseActionMode mode,
|
||||
uint comboRouteId,
|
||||
bool* outOptAreaTargeted
|
||||
);
|
||||
|
||||
public readonly Hook<UseActionDelegate> UseActionHook = null!;
|
||||
|
||||
public delegate byte IsActionHighlightedDelegate(ActionManager* manager, CSActionType actionType, uint actionId);
|
||||
public delegate byte IsActionHighlightedDelegate(
|
||||
ActionManager* manager,
|
||||
CSActionType actionType,
|
||||
uint actionId
|
||||
);
|
||||
|
||||
public readonly Hook<IsActionHighlightedDelegate> IsActionHighlightedHook = null!;
|
||||
|
||||
public Hooks()
|
||||
{
|
||||
UseActionHook = Service.GameInteropProvider.HookFromAddress<UseActionDelegate>((nint)ActionManager.MemberFunctionPointers.UseAction, UseActionDetour);
|
||||
IsActionHighlightedHook = Service.GameInteropProvider.HookFromAddress<IsActionHighlightedDelegate>((nint)ActionManager.MemberFunctionPointers.IsActionHighlighted, IsActionHighlightedDetour);
|
||||
UseActionHook = Service.GameInteropProvider.HookFromAddress<UseActionDelegate>(
|
||||
(nint)ActionManager.MemberFunctionPointers.UseAction,
|
||||
UseActionDetour
|
||||
);
|
||||
IsActionHighlightedHook =
|
||||
Service.GameInteropProvider.HookFromAddress<IsActionHighlightedDelegate>(
|
||||
(nint)ActionManager.MemberFunctionPointers.IsActionHighlighted,
|
||||
IsActionHighlightedDetour
|
||||
);
|
||||
|
||||
UseActionHook.Enable();
|
||||
IsActionHighlightedHook.Enable();
|
||||
}
|
||||
|
||||
private bool UseActionDetour(ActionManager* manager, CSActionType actionType, uint actionId, ulong targetId, uint extraParam, ActionManager.UseActionMode mode, uint comboRouteId, bool* optOutAreaTargeted)
|
||||
private bool UseActionDetour(
|
||||
ActionManager* manager,
|
||||
CSActionType actionType,
|
||||
uint actionId,
|
||||
ulong targetId,
|
||||
uint extraParam,
|
||||
ActionManager.UseActionMode mode,
|
||||
uint comboRouteId,
|
||||
bool* optOutAreaTargeted
|
||||
)
|
||||
{
|
||||
var canCast = manager->GetActionStatus(actionType, actionId) == 0;
|
||||
var ret = UseActionHook.Original(manager, actionType, actionId, targetId, extraParam, mode, comboRouteId, optOutAreaTargeted);
|
||||
var ret = UseActionHook.Original(
|
||||
manager,
|
||||
actionType,
|
||||
actionId,
|
||||
targetId,
|
||||
extraParam,
|
||||
mode,
|
||||
comboRouteId,
|
||||
optOutAreaTargeted
|
||||
);
|
||||
if (canCast && ret && actionType is CSActionType.CraftAction or CSActionType.Action)
|
||||
{
|
||||
var classJob = ClassJobUtils.GetClassJobFromIdx((byte)(Service.Objects.LocalPlayer?.ClassJob.RowId ?? 0));
|
||||
var classJob = ClassJobUtils.GetClassJobFromIdx(
|
||||
(byte)(Service.Objects.LocalPlayer?.ClassJob.RowId ?? 0)
|
||||
);
|
||||
if (classJob != null)
|
||||
{
|
||||
var simActionType = ActionUtils.GetActionTypeFromId(actionId, classJob.Value, actionType == CSActionType.CraftAction);
|
||||
var simActionType = ActionUtils.GetActionTypeFromId(
|
||||
actionId,
|
||||
classJob.Value,
|
||||
actionType == CSActionType.CraftAction
|
||||
);
|
||||
if (simActionType != null)
|
||||
{
|
||||
try
|
||||
@@ -57,7 +101,11 @@ public sealed unsafe class Hooks : IDisposable
|
||||
return ret;
|
||||
}
|
||||
|
||||
private byte IsActionHighlightedDetour(ActionManager* manager, CSActionType actionType, uint actionId)
|
||||
private byte IsActionHighlightedDetour(
|
||||
ActionManager* manager,
|
||||
CSActionType actionType,
|
||||
uint actionId
|
||||
)
|
||||
{
|
||||
var ret = IsActionHighlightedHook.Original(manager, actionType, actionId);
|
||||
|
||||
@@ -83,7 +131,11 @@ public sealed unsafe class Hooks : IDisposable
|
||||
if (classJob == null)
|
||||
return ret;
|
||||
|
||||
var simActionType = ActionUtils.GetActionTypeFromId(actionId, classJob.Value, actionType == CSActionType.CraftAction);
|
||||
var simActionType = ActionUtils.GetActionTypeFromId(
|
||||
actionId,
|
||||
classJob.Value,
|
||||
actionType == CSActionType.CraftAction
|
||||
);
|
||||
if (simActionType == null)
|
||||
return ret;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -52,7 +52,8 @@ public sealed class IconManager : IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
public IDalamudTextureWrap GetWrapOrEmpty() => GetWrap() ?? Service.DalamudAssetManager.Empty4X4;
|
||||
public IDalamudTextureWrap GetWrapOrEmpty() =>
|
||||
GetWrap() ?? Service.DalamudAssetManager.Empty4X4;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -85,7 +86,10 @@ public sealed class IconManager : IDisposable
|
||||
Service.TextureProvider.GetFromGameIcon(new GameIconLookup(id, itemHq: isHq));
|
||||
|
||||
private static ISharedImmediateTexture GetAssemblyTextureInternal(string filename) =>
|
||||
Service.TextureProvider.GetFromManifestResource(Assembly.GetExecutingAssembly(), $"Craftimizer.{filename}");
|
||||
Service.TextureProvider.GetFromManifestResource(
|
||||
Assembly.GetExecutingAssembly(),
|
||||
$"Craftimizer.{filename}"
|
||||
);
|
||||
|
||||
public static ILoadedTextureIcon GetIcon(uint id, bool isHq = false) =>
|
||||
new LoadedIcon(GetIconInternal(id, isHq));
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using DotNext.Reflection;
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Plugin;
|
||||
using DotNext.Collections.Generic;
|
||||
using DotNext.Reflection;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -39,16 +39,27 @@ public sealed class Ipc
|
||||
|
||||
var returnsVoid = typeMethod.ReturnType == typeof(void);
|
||||
|
||||
var propSubscriber = typeof(IDalamudPluginInterface).GetMethod("GetIpcSubscriber", typeMethod.GetParameters().Length + 1, [typeof(string)]);
|
||||
var propSubscriber = typeof(IDalamudPluginInterface).GetMethod(
|
||||
"GetIpcSubscriber",
|
||||
typeMethod.GetParameters().Length + 1,
|
||||
[typeof(string)]
|
||||
);
|
||||
if (propSubscriber is null)
|
||||
throw new InvalidOperationException("GetIpcSubscriber method not found");
|
||||
|
||||
var callGateSubscriber = propSubscriber.MakeGenericMethod([.. typeMethod.GetParameterTypes(), returnsVoid ? typeof(int) : typeMethod.ReturnType]).Invoke(Service.PluginInterface, [attr.Name ?? prop.Name]);
|
||||
var callGateSubscriber = propSubscriber
|
||||
.MakeGenericMethod([
|
||||
.. typeMethod.GetParameterTypes(),
|
||||
returnsVoid ? typeof(int) : typeMethod.ReturnType,
|
||||
])
|
||||
.Invoke(Service.PluginInterface, [attr.Name ?? prop.Name]);
|
||||
|
||||
if (callGateSubscriber is null)
|
||||
throw new InvalidOperationException("CallGateSubscriber is null");
|
||||
|
||||
var invokeFunc = callGateSubscriber.GetType().GetMethod(returnsVoid ? "InvokeAction" : "InvokeFunc");
|
||||
var invokeFunc = callGateSubscriber
|
||||
.GetType()
|
||||
.GetMethod(returnsVoid ? "InvokeAction" : "InvokeFunc");
|
||||
if (invokeFunc is null)
|
||||
throw new InvalidOperationException("Subscriber Invoke method not found");
|
||||
|
||||
@@ -62,7 +73,8 @@ public sealed class Ipc
|
||||
public Func<bool> MacroMateIsAvailable { get; private set; } = null!;
|
||||
|
||||
[IPCCall("MacroMate.CreateOrUpdateMacro")]
|
||||
public Func<string, string, string?, uint?, bool> MacroMateCreateMacro { get; private set; } = null!;
|
||||
public Func<string, string, string?, uint?, bool> MacroMateCreateMacro { get; private set; } =
|
||||
null!;
|
||||
|
||||
[IPCCall("MacroMate.ValidateGroupPath")]
|
||||
public Func<string, (bool, string?)> MacroMateValidateGroupPath { get; private set; } = null!;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Craftimizer.Plugin;
|
||||
using System;
|
||||
using Craftimizer.Plugin;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -8,5 +8,6 @@ public static class Log
|
||||
public static void Debug(string line) => Service.PluginLog.Debug(line);
|
||||
|
||||
public static void Error(string line) => Service.PluginLog.Error(line);
|
||||
|
||||
public static void Error(Exception e, string line) => Service.PluginLog.Error(e, line);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -20,13 +20,15 @@ public static class MacroCopy
|
||||
{
|
||||
if (actions.Count == 0)
|
||||
{
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = "Cannot copy an empty macro.",
|
||||
MinimizedText = "Cannot copy empty macro",
|
||||
Title = "Macro Not Copied",
|
||||
Type = NotificationType.Error
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content = "Cannot copy an empty macro.",
|
||||
MinimizedText = "Cannot copy empty macro",
|
||||
Title = "Macro Not Copied",
|
||||
Type = NotificationType.Error,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,9 +51,14 @@ public static class MacroCopy
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> GetMacros(IReadOnlyList<ActionType> actions, MacroCopyConfiguration config)
|
||||
private static List<string> GetMacros(
|
||||
IReadOnlyList<ActionType> actions,
|
||||
MacroCopyConfiguration config
|
||||
)
|
||||
{
|
||||
var mustSplit = (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro) && config.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate;
|
||||
var mustSplit =
|
||||
(config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro)
|
||||
&& config.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate;
|
||||
|
||||
var macros = new List<string>();
|
||||
|
||||
@@ -143,41 +150,54 @@ public static class MacroCopy
|
||||
{
|
||||
var config = Service.Configuration.MacroCopy;
|
||||
|
||||
int i, macroIdx;
|
||||
int i,
|
||||
macroIdx;
|
||||
for (
|
||||
i = 0, macroIdx = config.StartMacroIdx;
|
||||
i < macros.Count && i < config.MaxMacroCount && macroIdx < 100;
|
||||
i++, macroIdx += config.CopyDown ? 10 : 1)
|
||||
i++, macroIdx += config.CopyDown ? 10 : 1
|
||||
)
|
||||
SetMacro(macroIdx, config.SharedMacro, macros[i], i + 1);
|
||||
|
||||
if (config.ShowCopiedMessage)
|
||||
{
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = i > 1 ? "Copied macro to User Macros." : $"Copied {i} macros to User Macros.",
|
||||
MinimizedText = i > 1 ? "Copied macro" : $"Copied {i} macros",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content =
|
||||
i > 1
|
||||
? "Copied macro to User Macros."
|
||||
: $"Copied {i} macros to User Macros.",
|
||||
MinimizedText = i > 1 ? "Copied macro" : $"Copied {i} macros",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success,
|
||||
}
|
||||
);
|
||||
}
|
||||
if (i < macros.Count)
|
||||
{
|
||||
Service.Plugin.OpenMacroClipboard(macros);
|
||||
var rest = macros.Count - i;
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = $"Couldn't copy {rest} macro{(rest == 1 ? "" : "s")}, so a window was opened with all of them.",
|
||||
Minimized = false,
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Warning
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content =
|
||||
$"Couldn't copy {rest} macro{(rest == 1 ? "" : "s")}, so a window was opened with all of them.",
|
||||
Minimized = false,
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Warning,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetMacro(int idx, bool isShared, string macroText, int macroIdx)
|
||||
{
|
||||
if (idx >= 100 || idx < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(idx), "Macro index must be between 0 and 99");
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(idx),
|
||||
"Macro index must be between 0 and 99"
|
||||
);
|
||||
|
||||
var set = isShared ? 1u : 0u;
|
||||
|
||||
@@ -201,13 +221,19 @@ public static class MacroCopy
|
||||
ImGui.SetClipboardText(string.Join(Environment.NewLine + Environment.NewLine, macros));
|
||||
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
|
||||
{
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = macros.Count == 1 ? "Copied macro to clipboard." : $"Copied {macros.Count} macros to clipboard.",
|
||||
MinimizedText = macros.Count == 1 ? "Copied macro" : $"Copied {macros.Count} macros",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content =
|
||||
macros.Count == 1
|
||||
? "Copied macro to clipboard."
|
||||
: $"Copied {macros.Count} macros to clipboard.",
|
||||
MinimizedText =
|
||||
macros.Count == 1 ? "Copied macro" : $"Copied {macros.Count} macros",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,13 +241,15 @@ public static class MacroCopy
|
||||
{
|
||||
if (!Service.Ipc.MacroMateIsAvailable())
|
||||
{
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = "Please check if it installed and enabled.",
|
||||
MinimizedText = "Macro Mate is unavailable",
|
||||
Title = "Macro Mate Unavailable",
|
||||
Type = NotificationType.Error
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content = "Please check if it installed and enabled.",
|
||||
MinimizedText = "Macro Mate is unavailable",
|
||||
Title = "Macro Mate Unavailable",
|
||||
Type = NotificationType.Error,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -232,27 +260,36 @@ public static class MacroCopy
|
||||
var (isValidParent, parentError) = Service.Ipc.MacroMateValidateGroupPath(parentPath);
|
||||
if (!isValidParent)
|
||||
{
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = parentError!,
|
||||
MinimizedText = parentError,
|
||||
Title = "Macro Mate Invalid Parent",
|
||||
Type = NotificationType.Error
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content = parentError!,
|
||||
MinimizedText = parentError,
|
||||
Title = "Macro Mate Invalid Parent",
|
||||
Type = NotificationType.Error,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Service.Ipc.MacroMateCreateMacro(Service.Configuration.MacroCopy.MacroMateName, macro, parentPath, null);
|
||||
Service.Ipc.MacroMateCreateMacro(
|
||||
Service.Configuration.MacroCopy.MacroMateName,
|
||||
macro,
|
||||
parentPath,
|
||||
null
|
||||
);
|
||||
|
||||
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
|
||||
{
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = "Copied macro to Macro Mate.",
|
||||
MinimizedText = "Copied macro",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content = "Copied macro to Macro Mate.",
|
||||
MinimizedText = "Copied macro",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Dalamud.Networking.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -12,6 +8,10 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Dalamud.Networking.Http;
|
||||
using static Craftimizer.Utils.CommunityMacros;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
@@ -69,7 +69,10 @@ public static class MacroImport
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out uri!))
|
||||
return false;
|
||||
|
||||
if (!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
|
||||
if (
|
||||
!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
return false;
|
||||
|
||||
if (!uri.IsDefaultPort)
|
||||
@@ -87,34 +90,46 @@ public static class MacroImport
|
||||
{
|
||||
"ffxivteamcraft.com" => RetrieveTeamcraftUrl(uri, token),
|
||||
"craftingway.app" => RetrieveCraftingwayUrl(uri, token),
|
||||
_ => throw new UnreachableException("TryParseUrl should handle miscellaneous edge cases"),
|
||||
_ => throw new UnreachableException(
|
||||
"TryParseUrl should handle miscellaneous edge cases"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task<CommunityMacro> RetrieveTeamcraftUrl(Uri uri, CancellationToken token)
|
||||
{
|
||||
using var heCallback = new HappyEyeballsCallback();
|
||||
using var client = new HttpClient(new SocketsHttpHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
ConnectCallback = heCallback.ConnectCallback,
|
||||
});
|
||||
using var client = new HttpClient(
|
||||
new SocketsHttpHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
ConnectCallback = heCallback.ConnectCallback,
|
||||
}
|
||||
);
|
||||
|
||||
var path = uri.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);
|
||||
if (!path.StartsWith("simulator/", StringComparison.Ordinal))
|
||||
throw new ArgumentException("Teamcraft macro url should start with /simulator", nameof(uri));
|
||||
throw new ArgumentException(
|
||||
"Teamcraft macro url should start with /simulator",
|
||||
nameof(uri)
|
||||
);
|
||||
path = path[10..];
|
||||
|
||||
var lastSlash = path.LastIndexOf('/');
|
||||
if (lastSlash == -1)
|
||||
throw new ArgumentException("Teamcraft macro url is not in the right format", nameof(uri));
|
||||
throw new ArgumentException(
|
||||
"Teamcraft macro url is not in the right format",
|
||||
nameof(uri)
|
||||
);
|
||||
|
||||
var id = path[(lastSlash + 1)..];
|
||||
|
||||
var resp = await client.GetFromJsonAsync<TeamcraftMacro>(
|
||||
$"https://firestore.googleapis.com/v1beta1/projects/ffxivteamcraft/databases/(default)/documents/rotations/{id}",
|
||||
token).
|
||||
ConfigureAwait(false);
|
||||
var resp = await client
|
||||
.GetFromJsonAsync<TeamcraftMacro>(
|
||||
$"https://firestore.googleapis.com/v1beta1/projects/ffxivteamcraft/databases/(default)/documents/rotations/{id}",
|
||||
token
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
if (resp is null)
|
||||
throw new Exception("Internal error; failed to retrieve macro");
|
||||
if (resp.Error is { } error)
|
||||
@@ -122,31 +137,41 @@ public static class MacroImport
|
||||
return new(resp);
|
||||
}
|
||||
|
||||
private static async Task<CommunityMacro> RetrieveCraftingwayUrl(Uri uri, CancellationToken token)
|
||||
private static async Task<CommunityMacro> RetrieveCraftingwayUrl(
|
||||
Uri uri,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
using var heCallback = new HappyEyeballsCallback();
|
||||
using var client = new HttpClient(new SocketsHttpHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
ConnectCallback = heCallback.ConnectCallback,
|
||||
});
|
||||
using var client = new HttpClient(
|
||||
new SocketsHttpHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
ConnectCallback = heCallback.ConnectCallback,
|
||||
}
|
||||
);
|
||||
|
||||
// https://craftingway.app/rotation/variable-blueprint-KmrvS
|
||||
|
||||
var path = uri.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);
|
||||
if (!path.StartsWith("rotation/", StringComparison.Ordinal))
|
||||
throw new ArgumentException("Craftingway macro url should start with /rotation", nameof(uri));
|
||||
throw new ArgumentException(
|
||||
"Craftingway macro url should start with /rotation",
|
||||
nameof(uri)
|
||||
);
|
||||
path = path[9..];
|
||||
|
||||
var lastSlash = path.LastIndexOf('/');
|
||||
if (lastSlash != -1)
|
||||
throw new ArgumentException("Craftingway macro url is not in the right format", nameof(uri));
|
||||
throw new ArgumentException(
|
||||
"Craftingway macro url is not in the right format",
|
||||
nameof(uri)
|
||||
);
|
||||
|
||||
var id = path;
|
||||
|
||||
var resp = await client.GetFromJsonAsync<CraftingwayMacro>(
|
||||
$"https://servingway.fly.dev/rotation/{id}",
|
||||
token)
|
||||
var resp = await client
|
||||
.GetFromJsonAsync<CraftingwayMacro>($"https://servingway.fly.dev/rotation/{id}", token)
|
||||
.ConfigureAwait(false);
|
||||
if (resp is null)
|
||||
throw new Exception("Internal error; failed to retrieve macro");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Lumina.Excel.Sheets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Lumina.Excel.Sheets;
|
||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
@@ -28,16 +28,22 @@ public sealed record RecipeData
|
||||
{
|
||||
RecipeId = recipeId;
|
||||
|
||||
Recipe = LuminaSheets.RecipeSheet.GetRowOrDefault(recipeId) ??
|
||||
throw new ArgumentException($"Invalid recipe id {recipeId}", nameof(recipeId));
|
||||
Recipe =
|
||||
LuminaSheets.RecipeSheet.GetRowOrDefault(recipeId)
|
||||
?? throw new ArgumentException($"Invalid recipe id {recipeId}", nameof(recipeId));
|
||||
|
||||
ClassJob = (ClassJob)Recipe.CraftType.RowId;
|
||||
|
||||
var resolvedLevelTableRow = Recipe.RecipeLevelTable.RowId;
|
||||
if (Recipe.MaxAdjustableJobLevel.RowId != 0)
|
||||
{
|
||||
AdjustedJobLevel = Math.Min(explicitlyAdjustedJobLevel ?? ClassJob.GetWKSSyncedLevel(), (ushort)Recipe.MaxAdjustableJobLevel.RowId);
|
||||
resolvedLevelTableRow = LuminaSheets.GathererCrafterLvAdjustTableSheet.GetRow(AdjustedJobLevel.Value).RecipeLevel.RowId;
|
||||
AdjustedJobLevel = Math.Min(
|
||||
explicitlyAdjustedJobLevel ?? ClassJob.GetWKSSyncedLevel(),
|
||||
(ushort)Recipe.MaxAdjustableJobLevel.RowId
|
||||
);
|
||||
resolvedLevelTableRow = LuminaSheets
|
||||
.GathererCrafterLvAdjustTableSheet.GetRow(AdjustedJobLevel.Value)
|
||||
.RecipeLevel.RowId;
|
||||
}
|
||||
Table = LuminaSheets.RecipeLevelTableSheet.GetRow(resolvedLevelTableRow);
|
||||
|
||||
@@ -46,8 +52,14 @@ public sealed record RecipeData
|
||||
IsExpert = Recipe.IsExpert,
|
||||
ClassJobLevel = Table.ClassJobLevel,
|
||||
ConditionsFlag = Table.ConditionsFlag,
|
||||
MaxDurability = (Recipe.MaxAdjustableJobLevel.RowId != 0 ? 80 : Table.Durability) * Recipe.DurabilityFactor / 100,
|
||||
MaxQuality = (Recipe.CanHq || Recipe.RequiredQuality > 0) ? (int)Table.Quality * Recipe.QualityFactor / 100 : 0,
|
||||
MaxDurability =
|
||||
(Recipe.MaxAdjustableJobLevel.RowId != 0 ? 80 : Table.Durability)
|
||||
* Recipe.DurabilityFactor
|
||||
/ 100,
|
||||
MaxQuality =
|
||||
(Recipe.CanHq || Recipe.RequiredQuality > 0)
|
||||
? (int)Table.Quality * Recipe.QualityFactor / 100
|
||||
: 0,
|
||||
MaxProgress = Table.Difficulty * Recipe.DifficultyFactor / 100,
|
||||
QualityModifier = Table.QualityModifier,
|
||||
QualityDivider = Table.QualityDivider,
|
||||
@@ -64,23 +76,37 @@ public sealed record RecipeData
|
||||
{
|
||||
if (entry.ItemTradeIn.RowId == Recipe.ItemResult.RowId)
|
||||
{
|
||||
thresholds = [entry.BaseCollectableRating, entry.MidCollectableRating, entry.HighCollectableRating];
|
||||
thresholds =
|
||||
[
|
||||
entry.BaseCollectableRating,
|
||||
entry.MidCollectableRating,
|
||||
entry.HighCollectableRating,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Recipe.CollectableMetadata.GetValueOrDefaultSubrow<SatisfactionSupply>() is { } row3)
|
||||
else if (
|
||||
Recipe.CollectableMetadata.GetValueOrDefaultSubrow<SatisfactionSupply>() is { } row3
|
||||
)
|
||||
{
|
||||
foreach (var subrow in row3)
|
||||
{
|
||||
if (subrow.Item.RowId == Recipe.ItemResult.RowId)
|
||||
{
|
||||
thresholds = [subrow.CollectabilityLow, subrow.CollectabilityMid, subrow.CollectabilityHigh];
|
||||
thresholds =
|
||||
[
|
||||
subrow.CollectabilityLow,
|
||||
subrow.CollectabilityMid,
|
||||
subrow.CollectabilityHigh,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Recipe.CollectableMetadata.GetValueOrDefault<SharlayanCraftWorksSupply>() is { } row5)
|
||||
else if (
|
||||
Recipe.CollectableMetadata.GetValueOrDefault<SharlayanCraftWorksSupply>() is { } row5
|
||||
)
|
||||
{
|
||||
foreach (var item in row5.Item)
|
||||
{
|
||||
@@ -93,10 +119,19 @@ public sealed record RecipeData
|
||||
}
|
||||
else if (Recipe.CollectableMetadata.GetValueOrDefault<CollectablesRefine>() is { } row6)
|
||||
thresholds = [row6.CollectabilityLow, row6.CollectabilityMid, row6.CollectabilityHigh];
|
||||
else if (Recipe.CollectableMetadataKey == 7 && LuminaSheets.WKSMissionToDoEvalutionRefinSheet.TryGetRow(Recipe.CollectableMetadata.RowId, out var row7))
|
||||
else if (
|
||||
Recipe.CollectableMetadataKey == 7
|
||||
&& LuminaSheets.WKSMissionToDoEvalutionRefinSheet.TryGetRow(
|
||||
Recipe.CollectableMetadata.RowId,
|
||||
out var row7
|
||||
)
|
||||
)
|
||||
{
|
||||
thresholds = [row7.Unknown0, row7.Unknown1, row7.Unknown2];
|
||||
thresholds = [.. thresholds.Select(percentage => RecipeInfo.MaxQuality * percentage / 1000)];
|
||||
thresholds =
|
||||
[
|
||||
.. thresholds.Select(percentage => RecipeInfo.MaxQuality * percentage / 1000),
|
||||
];
|
||||
}
|
||||
|
||||
if (thresholds != null)
|
||||
@@ -106,14 +141,17 @@ public sealed record RecipeData
|
||||
CollectableThresholds = t.ToArray();
|
||||
}
|
||||
|
||||
Ingredients = Recipe.Ingredient.Zip(Recipe.AmountIngredient)
|
||||
Ingredients = Recipe
|
||||
.Ingredient.Zip(Recipe.AmountIngredient)
|
||||
.Take(6)
|
||||
.Where(i => i.First.IsValid)
|
||||
.Select(i => (i.First.Value, (int)i.Second))
|
||||
.ToList();
|
||||
MaxStartingQuality = (int)Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
|
||||
MaxStartingQuality = (int)
|
||||
Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
|
||||
|
||||
TotalHqILvls = (int)Ingredients.Where(i => i.Item.CanBeHq).Sum(i => i.Item.LevelItem.RowId * i.Amount);
|
||||
TotalHqILvls = (int)
|
||||
Ingredients.Where(i => i.Item.CanBeHq).Sum(i => i.Item.LevelItem.RowId * i.Amount);
|
||||
}
|
||||
|
||||
public int CalculateItemStartingQuality(int itemIdx, int amount)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using DotNext.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sim = Craftimizer.Simulator.Simulator;
|
||||
using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom;
|
||||
|
||||
@@ -51,11 +51,11 @@ internal sealed class SimulatedMacro
|
||||
Average = (float)DataList.Average();
|
||||
}
|
||||
|
||||
public ImGuiUtils.ViolinData? GetViolinData(float barMax, int resolution, double bandwidth) =>
|
||||
ViolinData ??=
|
||||
Min != Max ?
|
||||
new(DataList, 0, barMax, resolution, bandwidth) :
|
||||
null;
|
||||
public ImGuiUtils.ViolinData? GetViolinData(
|
||||
float barMax,
|
||||
int resolution,
|
||||
double bandwidth
|
||||
) => ViolinData ??= Min != Max ? new(DataList, 0, barMax, resolution, bandwidth) : null;
|
||||
}
|
||||
|
||||
public readonly Param Progress = new();
|
||||
@@ -64,7 +64,12 @@ internal sealed class SimulatedMacro
|
||||
// Param is either collectability, quality, or hq%, depending on the recipe
|
||||
public readonly Param ParamScore = new();
|
||||
|
||||
public Reliablity(in SimulationState startState, IEnumerable<ActionType> actions, int iterCount, RecipeData recipeData)
|
||||
public Reliablity(
|
||||
in SimulationState startState,
|
||||
IEnumerable<ActionType> actions,
|
||||
int iterCount,
|
||||
RecipeData recipeData
|
||||
)
|
||||
{
|
||||
Func<SimulationState, int> getParam;
|
||||
if (recipeData.IsCollectable)
|
||||
@@ -97,12 +102,18 @@ internal sealed class SimulatedMacro
|
||||
{
|
||||
public ActionType Action { get; }
|
||||
public bool IsEphemeral { get; }
|
||||
|
||||
// State *after* executing the action
|
||||
public ActionResponse Response { get; private set; }
|
||||
public SimulationState State { get; private set; }
|
||||
private Reliablity? Reliability { get; set; }
|
||||
|
||||
public Step(ActionType action, Sim sim, in SimulationState lastState, out SimulationState newState)
|
||||
public Step(
|
||||
ActionType action,
|
||||
Sim sim,
|
||||
in SimulationState lastState,
|
||||
out SimulationState newState
|
||||
)
|
||||
{
|
||||
Action = action;
|
||||
newState = Recalculate(sim, lastState);
|
||||
@@ -122,9 +133,17 @@ internal sealed class SimulatedMacro
|
||||
return State;
|
||||
}
|
||||
|
||||
public Reliablity GetReliability(in SimulationState initialState, IEnumerable<ActionType> actionSet, RecipeData recipeData) =>
|
||||
Reliability ??=
|
||||
new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData);
|
||||
public Reliablity GetReliability(
|
||||
in SimulationState initialState,
|
||||
IEnumerable<ActionType> actionSet,
|
||||
RecipeData recipeData
|
||||
) =>
|
||||
Reliability ??= new(
|
||||
initialState,
|
||||
actionSet,
|
||||
Service.Configuration.ReliabilitySimulationCount,
|
||||
recipeData
|
||||
);
|
||||
};
|
||||
|
||||
private List<Step> Macro { get; set; } = [];
|
||||
@@ -162,9 +181,9 @@ internal sealed class SimulatedMacro
|
||||
}
|
||||
|
||||
public Reliablity GetReliability(RecipeData recipeData, Index? idx = null) =>
|
||||
Macro.Count > 0 ?
|
||||
Macro[idx ?? ^1].GetReliability(InitialState, Actions.ToArray(), recipeData) :
|
||||
new(InitialState, Array.Empty<ActionType>(), 0, recipeData);
|
||||
Macro.Count > 0
|
||||
? Macro[idx ?? ^1].GetReliability(InitialState, Actions.ToArray(), recipeData)
|
||||
: new(InitialState, Array.Empty<ActionType>(), 0, recipeData);
|
||||
|
||||
private void TryRecalculateFrom(int index)
|
||||
{
|
||||
@@ -177,14 +196,11 @@ internal sealed class SimulatedMacro
|
||||
state = Macro[i].Recalculate(sim, state);
|
||||
}
|
||||
|
||||
public void RecalculateState() =>
|
||||
TryRecalculateFrom(0);
|
||||
public void RecalculateState() => TryRecalculateFrom(0);
|
||||
|
||||
public void RemoveRange(int index, int count) =>
|
||||
Macro.RemoveRange(index, count);
|
||||
public void RemoveRange(int index, int count) => Macro.RemoveRange(index, count);
|
||||
|
||||
public void Clear() =>
|
||||
Macro.Clear();
|
||||
public void Clear() => Macro.Clear();
|
||||
|
||||
public void Add(ActionType action)
|
||||
{
|
||||
@@ -247,7 +263,10 @@ internal sealed class SimulatedMacro
|
||||
QueuedEphemeralSteps.Clear();
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (maxSize is { } size && QueuedSteps.Count + QueuedEphemeralSteps.Count + Macro.Count >= size)
|
||||
if (
|
||||
maxSize is { } size
|
||||
&& QueuedSteps.Count + QueuedEphemeralSteps.Count + Macro.Count >= size
|
||||
)
|
||||
return size;
|
||||
|
||||
QueuedEphemeralSteps.Add(new(action, true));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Dalamud.Game.Text;
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
|
||||
@@ -10,7 +10,10 @@ public static class SqText
|
||||
{
|
||||
public static SeIconChar LevelPrefix => SeIconChar.LevelEn;
|
||||
|
||||
public static readonly FrozenDictionary<char, SeIconChar> LevelNumReplacements = new Dictionary<char, SeIconChar>
|
||||
public static readonly FrozenDictionary<char, SeIconChar> LevelNumReplacements = new Dictionary<
|
||||
char,
|
||||
SeIconChar
|
||||
>
|
||||
{
|
||||
['0'] = SeIconChar.Number0,
|
||||
['1'] = SeIconChar.Number1,
|
||||
@@ -24,17 +27,18 @@ public static class SqText
|
||||
['9'] = SeIconChar.Number9,
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
public static string ToLevelString<T>(T value) where T : IBinaryInteger<T>
|
||||
public static string ToLevelString<T>(T value)
|
||||
where T : IBinaryInteger<T>
|
||||
{
|
||||
var str = value.ToString() ?? throw new FormatException("Failed to format value");
|
||||
foreach(var (k, v) in LevelNumReplacements)
|
||||
foreach (var (k, v) in LevelNumReplacements)
|
||||
str = str.Replace(k, v.ToIconChar());
|
||||
return str;
|
||||
}
|
||||
|
||||
public static bool TryParseLevelString(string str, out int result)
|
||||
{
|
||||
foreach(var (k, v) in LevelNumReplacements)
|
||||
foreach (var (k, v) in LevelNumReplacements)
|
||||
str = str.Replace(v.ToIconChar(), k);
|
||||
return int.TryParse(str, out result);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using Craftimizer.Simulator;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using System;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
|
||||
|
||||
namespace Craftimizer.Utils;
|
||||
@@ -12,7 +12,8 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
|
||||
{
|
||||
private AddonSynthesis* Addon { get; } = addon;
|
||||
|
||||
private ReadOnlySpan<AtkValue> Values => new(Addon->AtkUnitBase.AtkValues, Addon->AtkUnitBase.AtkValuesCount);
|
||||
private ReadOnlySpan<AtkValue> Values =>
|
||||
new(Addon->AtkUnitBase.AtkValues, Addon->AtkUnitBase.AtkValuesCount);
|
||||
|
||||
// Always 0?
|
||||
private uint IsInitializing => GetUInt(0);
|
||||
@@ -51,9 +52,7 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
|
||||
if (Addon == null)
|
||||
return null;
|
||||
var value = Values[i];
|
||||
return value.Type == ValueType.UInt ?
|
||||
value.UInt :
|
||||
null;
|
||||
return value.Type == ValueType.UInt ? value.UInt : null;
|
||||
}
|
||||
|
||||
private bool? TryGetBool(int i)
|
||||
@@ -61,9 +60,7 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
|
||||
if (Addon == null)
|
||||
return null;
|
||||
var value = Values[i];
|
||||
return value.Type == ValueType.Bool ?
|
||||
value.Byte != 0 :
|
||||
null;
|
||||
return value.Type == ValueType.Bool ? value.Byte != 0 : null;
|
||||
}
|
||||
|
||||
private SeString? TryGetString(int i)
|
||||
@@ -73,10 +70,10 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
|
||||
var value = Values[i];
|
||||
return value.Type switch
|
||||
{
|
||||
ValueType.ManagedString or
|
||||
ValueType.String =>
|
||||
MemoryHelper.ReadSeStringNullTerminated((nint)value.String.Value),
|
||||
_ => null
|
||||
ValueType.ManagedString or ValueType.String => MemoryHelper.ReadSeStringNullTerminated(
|
||||
(nint)value.String.Value
|
||||
),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Craftimizer.Plugin;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
|
||||
namespace Craftimizer.Windows;
|
||||
|
||||
@@ -17,7 +17,8 @@ public sealed class MacroClipboard : Window, IDisposable
|
||||
|
||||
private List<string> Macros { get; }
|
||||
|
||||
public MacroClipboard(IEnumerable<string> macros) : base("Macro Clipboard", WindowFlags)
|
||||
public MacroClipboard(IEnumerable<string> macros)
|
||||
: base("Macro Clipboard", WindowFlags)
|
||||
{
|
||||
Macros = [.. macros];
|
||||
|
||||
@@ -39,32 +40,49 @@ public sealed class MacroClipboard : Window, IDisposable
|
||||
private void DrawMacro(int idx, string macro)
|
||||
{
|
||||
using var id = ImRaii.PushId(idx);
|
||||
using var panel = ImRaii2.GroupPanel(Macros.Count == 1 ? "Macro" : $"Macro {idx + 1}", -1, out var availWidth);
|
||||
using var panel = ImRaii2.GroupPanel(
|
||||
Macros.Count == 1 ? "Macro" : $"Macro {idx + 1}",
|
||||
-1,
|
||||
out var availWidth
|
||||
);
|
||||
|
||||
var cursor = ImGui.GetCursorPos();
|
||||
|
||||
ImGuiUtils.AlignRight(ImGui.GetFrameHeight(), availWidth);
|
||||
var buttonCursor = ImGui.GetCursorPos();
|
||||
ImGui.InvisibleButton("##copyInvButton", new(ImGui.GetFrameHeight()));
|
||||
var buttonHovered = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenOverlapped | ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
|
||||
var buttonHovered = ImGui.IsItemHovered(
|
||||
ImGuiHoveredFlags.AllowWhenOverlapped | ImGuiHoveredFlags.AllowWhenBlockedByActiveItem
|
||||
);
|
||||
var buttonActive = buttonHovered && ImGui.GetIO().MouseDown[(int)ImGuiMouseButton.Left];
|
||||
var buttonClicked = buttonHovered && ImGui.GetIO().MouseReleased[(int)ImGuiMouseButton.Left];
|
||||
var buttonClicked =
|
||||
buttonHovered && ImGui.GetIO().MouseReleased[(int)ImGuiMouseButton.Left];
|
||||
ImGui.SetCursorPos(buttonCursor);
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(buttonActive ? ImGuiCol.ButtonActive : ImGuiCol.ButtonHovered), buttonHovered);
|
||||
using var color = ImRaii.PushColor(
|
||||
ImGuiCol.Button,
|
||||
ImGui.GetColorU32(buttonActive ? ImGuiCol.ButtonActive : ImGuiCol.ButtonHovered),
|
||||
buttonHovered
|
||||
);
|
||||
ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste);
|
||||
if (buttonClicked)
|
||||
{
|
||||
ImGui.SetClipboardText(macro);
|
||||
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
|
||||
{
|
||||
Plugin.Plugin.DisplayNotification(new()
|
||||
{
|
||||
Content = Macros.Count == 1 ? "Copied macro to clipboard." : $"Copied macro {idx + 1} to clipboard.",
|
||||
MinimizedText = Macros.Count == 1 ? "Copied macro" : $"Copied macro {idx + 1}",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success
|
||||
});
|
||||
Plugin.Plugin.DisplayNotification(
|
||||
new()
|
||||
{
|
||||
Content =
|
||||
Macros.Count == 1
|
||||
? "Copied macro to clipboard."
|
||||
: $"Copied macro {idx + 1} to clipboard.",
|
||||
MinimizedText =
|
||||
Macros.Count == 1 ? "Copied macro" : $"Copied macro {idx + 1}",
|
||||
Title = "Macro Copied",
|
||||
Type = NotificationType.Success,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +95,17 @@ public sealed class MacroClipboard : Window, IDisposable
|
||||
using var padding = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
||||
using var bg = ImRaii.PushColor(ImGuiCol.FrameBg, Vector4.Zero);
|
||||
var lineCount = macro.Count(c => c == '\n') + 1;
|
||||
ImGui.InputTextMultiline("", ref macro, macro.Length + 1, new(availWidth, ImGui.GetTextLineHeight() * Math.Max(15, lineCount) + ImGui.GetStyle().FramePadding.Y), ImGuiInputTextFlags.ReadOnly | ImGuiInputTextFlags.AutoSelectAll);
|
||||
ImGui.InputTextMultiline(
|
||||
"",
|
||||
ref macro,
|
||||
macro.Length + 1,
|
||||
new(
|
||||
availWidth,
|
||||
ImGui.GetTextLineHeight() * Math.Max(15, lineCount)
|
||||
+ ImGui.GetStyle().FramePadding.Y
|
||||
),
|
||||
ImGuiInputTextFlags.ReadOnly | ImGuiInputTextFlags.AutoSelectAll
|
||||
);
|
||||
}
|
||||
|
||||
if (buttonHovered)
|
||||
|
||||
+743
-232
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,18 @@
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Utility;
|
||||
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
|
||||
|
||||
namespace Craftimizer.Windows;
|
||||
|
||||
@@ -26,7 +26,8 @@ public sealed class MacroList : Window, IDisposable
|
||||
private static IReadOnlyList<Macro> Macros => Service.Configuration.Macros;
|
||||
private Dictionary<Macro, SimulationState> MacroStateCache { get; } = [];
|
||||
|
||||
public MacroList() : base("Craftimizer Macro List", WindowFlags, false)
|
||||
public MacroList()
|
||||
: base("Craftimizer Macro List", WindowFlags, false)
|
||||
{
|
||||
RefreshSearch();
|
||||
|
||||
@@ -36,7 +37,11 @@ public sealed class MacroList : Window, IDisposable
|
||||
CollapsedCondition = ImGuiCond.Appearing;
|
||||
Collapsed = false;
|
||||
|
||||
SizeConstraints = new() { MinimumSize = new(465, 520), MaximumSize = new(float.PositiveInfinity) };
|
||||
SizeConstraints = new()
|
||||
{
|
||||
MinimumSize = new(465, 520),
|
||||
MaximumSize = new(float.PositiveInfinity),
|
||||
};
|
||||
|
||||
TitleBarButtons =
|
||||
[
|
||||
@@ -45,14 +50,15 @@ public sealed class MacroList : Window, IDisposable
|
||||
Icon = FontAwesomeIcon.Cog,
|
||||
IconOffset = new(2, 1),
|
||||
Click = _ => Service.Plugin.OpenSettingsTab("General"),
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings")
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings"),
|
||||
},
|
||||
new() {
|
||||
new()
|
||||
{
|
||||
Icon = FontAwesomeIcon.Heart,
|
||||
IconOffset = new(2, 1),
|
||||
Click = _ => Util.OpenLink(Plugin.Plugin.SupportLink),
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!")
|
||||
}
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!"),
|
||||
},
|
||||
];
|
||||
|
||||
Service.WindowSystem.AddWindow(this);
|
||||
@@ -90,7 +96,9 @@ public sealed class MacroList : Window, IDisposable
|
||||
ImGui.InvisibleButton($"###macroButton{i}", ImGui.GetItemRectSize());
|
||||
if (isUnsorted)
|
||||
{
|
||||
using (var _source = ImRaii.DragDropSource(ImGuiDragDropFlags.SourceNoDisableHover))
|
||||
using (
|
||||
var _source = ImRaii.DragDropSource(ImGuiDragDropFlags.SourceNoDisableHover)
|
||||
)
|
||||
{
|
||||
if (_source)
|
||||
{
|
||||
@@ -115,7 +123,10 @@ public sealed class MacroList : Window, IDisposable
|
||||
var text2 = "the Macro Editor here or from the Crafting Log.";
|
||||
var text3 = "Open Crafting Log";
|
||||
var text4 = "Open Macro Editor";
|
||||
var buttonRowWidth = ImGui.CalcTextSize(text3).X + ImGui.CalcTextSize(text4).X + ImGui.GetStyle().ItemSpacing.X * 5;
|
||||
var buttonRowWidth =
|
||||
ImGui.CalcTextSize(text3).X
|
||||
+ ImGui.CalcTextSize(text4).X
|
||||
+ ImGui.GetStyle().ItemSpacing.X * 5;
|
||||
var size = new Vector2(
|
||||
Math.Max(
|
||||
Math.Max(ImGui.CalcTextSize(text1).X, ImGui.CalcTextSize(text2).X),
|
||||
@@ -139,6 +150,7 @@ public sealed class MacroList : Window, IDisposable
|
||||
private string searchText = string.Empty;
|
||||
private List<Macro> sortedMacros = null!;
|
||||
private bool isUnsorted = true;
|
||||
|
||||
private void DrawSearchBar()
|
||||
{
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
@@ -157,12 +169,20 @@ public sealed class MacroList : Window, IDisposable
|
||||
|
||||
var stateNullable = GetMacroState(macro);
|
||||
|
||||
using var panel = ImRaii2.GroupPanel(macro.Name, width - ImGui.GetStyle().ItemSpacing.X * 2, out var availWidth);
|
||||
using var panel = ImRaii2.GroupPanel(
|
||||
macro.Name,
|
||||
width - ImGui.GetStyle().ItemSpacing.X * 2,
|
||||
out var availWidth
|
||||
);
|
||||
var stepsAvailWidthOffset = width - availWidth;
|
||||
var spacing = ImGui.GetStyle().ItemSpacing.Y;
|
||||
var miniRowHeight = (windowHeight - spacing) / 2f;
|
||||
|
||||
using var table = ImRaii.Table("table", stateNullable.HasValue ? 3 : 2, ImGuiTableFlags.BordersInnerV);
|
||||
using var table = ImRaii.Table(
|
||||
"table",
|
||||
stateNullable.HasValue ? 3 : 2,
|
||||
ImGuiTableFlags.BordersInnerV
|
||||
);
|
||||
if (table)
|
||||
{
|
||||
if (stateNullable.HasValue)
|
||||
@@ -177,27 +197,36 @@ public sealed class MacroList : Window, IDisposable
|
||||
if (Service.Configuration.ShowOptimalMacroStat)
|
||||
{
|
||||
var progressHeight = windowHeight;
|
||||
if (state.Progress >= state.Input.Recipe.MaxProgress && state.Input.Recipe.MaxQuality > 0)
|
||||
if (
|
||||
state.Progress >= state.Input.Recipe.MaxProgress
|
||||
&& state.Input.Recipe.MaxQuality > 0
|
||||
)
|
||||
{
|
||||
ImGuiUtils.ArcProgress(
|
||||
(float)state.Quality / state.Input.Recipe.MaxQuality,
|
||||
progressHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.Quality));
|
||||
(float)state.Quality / state.Input.Recipe.MaxQuality,
|
||||
progressHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.Quality)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip($"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}");
|
||||
ImGuiUtils.Tooltip(
|
||||
$"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtils.ArcProgress(
|
||||
(float)state.Progress / state.Input.Recipe.MaxProgress,
|
||||
progressHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.Progress));
|
||||
(float)state.Progress / state.Input.Recipe.MaxProgress,
|
||||
progressHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.Progress)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip($"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}");
|
||||
ImGuiUtils.Tooltip(
|
||||
$"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}"
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -207,9 +236,12 @@ public sealed class MacroList : Window, IDisposable
|
||||
miniRowHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.Progress));
|
||||
ImGui.GetColorU32(Colors.Progress)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip($"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}");
|
||||
ImGuiUtils.Tooltip(
|
||||
$"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}"
|
||||
);
|
||||
|
||||
ImGui.SameLine(0, spacing);
|
||||
ImGuiUtils.ArcProgress(
|
||||
@@ -217,17 +249,24 @@ public sealed class MacroList : Window, IDisposable
|
||||
miniRowHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.Quality));
|
||||
ImGui.GetColorU32(Colors.Quality)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip($"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}");
|
||||
ImGuiUtils.Tooltip(
|
||||
$"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}"
|
||||
);
|
||||
|
||||
ImGuiUtils.ArcProgress((float)state.Durability / state.Input.Recipe.MaxDurability,
|
||||
ImGuiUtils.ArcProgress(
|
||||
(float)state.Durability / state.Input.Recipe.MaxDurability,
|
||||
miniRowHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.Durability));
|
||||
ImGui.GetColorU32(Colors.Durability)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip($"Remaining Durability: {state.Durability} / {state.Input.Recipe.MaxDurability}");
|
||||
ImGuiUtils.Tooltip(
|
||||
$"Remaining Durability: {state.Durability} / {state.Input.Recipe.MaxDurability}"
|
||||
);
|
||||
|
||||
ImGui.SameLine(0, spacing);
|
||||
ImGuiUtils.ArcProgress(
|
||||
@@ -235,7 +274,8 @@ public sealed class MacroList : Window, IDisposable
|
||||
miniRowHeight / 2f,
|
||||
.5f,
|
||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||
ImGui.GetColorU32(Colors.CP));
|
||||
ImGui.GetColorU32(Colors.CP)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip($"Remaining CP: {state.CP} / {state.Input.Stats.CP}");
|
||||
}
|
||||
@@ -270,7 +310,11 @@ public sealed class MacroList : Window, IDisposable
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
{
|
||||
var itemsPerRow = (int)MathF.Floor((ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset + spacing * 2) / (miniRowHeight + spacing));
|
||||
var itemsPerRow = (int)
|
||||
MathF.Floor(
|
||||
(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset + spacing * 2)
|
||||
/ (miniRowHeight + spacing)
|
||||
);
|
||||
var itemCount = macro.Actions.Count;
|
||||
for (var i = 0; i < itemsPerRow * 2; i++)
|
||||
{
|
||||
@@ -281,7 +325,10 @@ public sealed class MacroList : Window, IDisposable
|
||||
var shouldShowMore = i + 1 == itemsPerRow * 2 && i + 1 < itemCount;
|
||||
if (!shouldShowMore)
|
||||
{
|
||||
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle, new(miniRowHeight));
|
||||
ImGui.Image(
|
||||
macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle,
|
||||
new(miniRowHeight)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip(macro.Actions[i].GetName(RecipeData!.ClassJob));
|
||||
}
|
||||
@@ -289,12 +336,36 @@ public sealed class MacroList : Window, IDisposable
|
||||
{
|
||||
var amtMore = itemCount - itemsPerRow * 2;
|
||||
var pos = ImGui.GetCursorPos();
|
||||
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle, new(miniRowHeight), default, Vector2.One, new(1, 1, 1, .5f));
|
||||
ImGui.Image(
|
||||
macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle,
|
||||
new(miniRowHeight),
|
||||
default,
|
||||
Vector2.One,
|
||||
new(1, 1, 1, .5f)
|
||||
);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.Tooltip($"{macro.Actions[i].GetName(RecipeData!.ClassJob)}\nand {amtMore} more");
|
||||
ImGuiUtils.Tooltip(
|
||||
$"{macro.Actions[i].GetName(RecipeData!.ClassJob)}\nand {amtMore} more"
|
||||
);
|
||||
ImGui.SetCursorPos(pos);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), ImGui.GetColorU32(ImGuiCol.FrameBg), miniRowHeight / 8f);
|
||||
ImGui.GetWindowDrawList().AddTextClippedEx(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), $"+{amtMore}", null, new(.5f), null);
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRectFilled(
|
||||
ImGui.GetCursorScreenPos(),
|
||||
ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight),
|
||||
ImGui.GetColorU32(ImGuiCol.FrameBg),
|
||||
miniRowHeight / 8f
|
||||
);
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddTextClippedEx(
|
||||
ImGui.GetCursorScreenPos(),
|
||||
ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight),
|
||||
$"+{amtMore}",
|
||||
null,
|
||||
new(.5f),
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -306,12 +377,19 @@ public sealed class MacroList : Window, IDisposable
|
||||
|
||||
private string popupMacroName = string.Empty;
|
||||
private Macro? popupMacro;
|
||||
|
||||
private void ShowRenamePopup(Macro macro)
|
||||
{
|
||||
ImGui.OpenPopup($"##renamePopup-{macro.GetHashCode()}");
|
||||
popupMacro = macro;
|
||||
popupMacroName = macro.Name;
|
||||
ImGui.SetNextWindowPos(ImGui.GetMousePos() - new Vector2(ImGui.CalcItemWidth() * .25f, ImGui.GetFrameHeight() + ImGui.GetStyle().WindowPadding.Y * 2));
|
||||
ImGui.SetNextWindowPos(
|
||||
ImGui.GetMousePos()
|
||||
- new Vector2(
|
||||
ImGui.CalcItemWidth() * .25f,
|
||||
ImGui.GetFrameHeight() + ImGui.GetStyle().WindowPadding.Y * 2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawRenamePopup(Macro macro)
|
||||
@@ -322,7 +400,15 @@ public sealed class MacroList : Window, IDisposable
|
||||
if (ImGui.IsWindowAppearing())
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
ImGui.SetNextItemWidth(ImGui.CalcItemWidth());
|
||||
if (ImGui.InputTextWithHint($"##setName", "Name", ref popupMacroName, 100, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
if (
|
||||
ImGui.InputTextWithHint(
|
||||
$"##setName",
|
||||
"Name",
|
||||
ref popupMacroName,
|
||||
100,
|
||||
ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue
|
||||
)
|
||||
)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(popupMacroName))
|
||||
{
|
||||
@@ -349,7 +435,9 @@ public sealed class MacroList : Window, IDisposable
|
||||
}
|
||||
isUnsorted = false;
|
||||
var matcher = new FuzzyMatcher(searchText.ToLowerInvariant(), MatchMode.FuzzyParts);
|
||||
var query = Macros.AsParallel().Select(i => (Item: i, Score: matcher.Matches(i.Name.ToLowerInvariant())))
|
||||
var query = Macros
|
||||
.AsParallel()
|
||||
.Select(i => (Item: i, Score: matcher.Matches(i.Name.ToLowerInvariant())))
|
||||
.Where(t => t.Score > 0)
|
||||
.OrderByDescending(t => t.Score)
|
||||
.Select(t => t.Item);
|
||||
@@ -359,7 +447,22 @@ public sealed class MacroList : Window, IDisposable
|
||||
private static void OpenEditor(Macro? macro)
|
||||
{
|
||||
var stats = Service.Plugin.GetDefaultStats();
|
||||
Service.Plugin.OpenMacroEditor(stats.Character, stats.Recipe, stats.Buffs, null, macro?.Actions ?? Enumerable.Empty<ActionType>(), macro != null ? (actions => { macro.ActionEnumerable = actions; Service.Configuration.Save(); }) : null);
|
||||
Service.Plugin.OpenMacroEditor(
|
||||
stats.Character,
|
||||
stats.Recipe,
|
||||
stats.Buffs,
|
||||
null,
|
||||
macro?.Actions ?? Enumerable.Empty<ActionType>(),
|
||||
macro != null
|
||||
? (
|
||||
actions =>
|
||||
{
|
||||
macro.ActionEnumerable = actions;
|
||||
Service.Configuration.Save();
|
||||
}
|
||||
)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
private void OnMacroChanged(Macro macro)
|
||||
|
||||
+500
-193
File diff suppressed because it is too large
Load Diff
+330
-175
@@ -1,6 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Solver;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
@@ -8,12 +14,6 @@ using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace Craftimizer.Plugin.Windows;
|
||||
|
||||
@@ -31,17 +31,22 @@ public sealed class Settings : Window, IDisposable
|
||||
private IFontHandle HeaderFont { get; }
|
||||
private IFontHandle SubheaderFont { get; }
|
||||
|
||||
public Settings() : base("Craftimizer Settings", WindowFlags)
|
||||
public Settings()
|
||||
: base("Craftimizer Settings", WindowFlags)
|
||||
{
|
||||
Service.WindowSystem.AddWindow(this);
|
||||
|
||||
HeaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 2f)));
|
||||
SubheaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 1.5f)));
|
||||
HeaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||
e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 2f))
|
||||
);
|
||||
SubheaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||
e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 1.5f))
|
||||
);
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
{
|
||||
MinimumSize = new(450, 400),
|
||||
MaximumSize = new(float.PositiveInfinity)
|
||||
MaximumSize = new(float.PositiveInfinity),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,7 +66,13 @@ public sealed class Settings : Window, IDisposable
|
||||
return ImRaii.TabItem(label);
|
||||
}
|
||||
|
||||
private static void DrawOption(string label, string tooltip, bool val, Action<bool> setter, ref bool isDirty)
|
||||
private static void DrawOption(
|
||||
string label,
|
||||
string tooltip,
|
||||
bool val,
|
||||
Action<bool> setter,
|
||||
ref bool isDirty
|
||||
)
|
||||
{
|
||||
if (ImGui.Checkbox(label, ref val))
|
||||
{
|
||||
@@ -72,12 +83,28 @@ public sealed class Settings : Window, IDisposable
|
||||
ImGuiUtils.TooltipWrapped(tooltip);
|
||||
}
|
||||
|
||||
private static void DrawOption<T>(string label, string tooltip, T value, T min, T max, Action<T> setter, ref bool isDirty) where T : struct, INumber<T>
|
||||
private static void DrawOption<T>(
|
||||
string label,
|
||||
string tooltip,
|
||||
T value,
|
||||
T min,
|
||||
T max,
|
||||
Action<T> setter,
|
||||
ref bool isDirty
|
||||
)
|
||||
where T : struct, INumber<T>
|
||||
{
|
||||
ImGui.SetNextItemWidth(OptionWidth);
|
||||
var text = value.ToString();
|
||||
ArgumentNullException.ThrowIfNull(text, nameof(value));
|
||||
if (ImGui.InputText(label, ref text, 8, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CharsDecimal))
|
||||
if (
|
||||
ImGui.InputText(
|
||||
label,
|
||||
ref text,
|
||||
8,
|
||||
ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CharsDecimal
|
||||
)
|
||||
)
|
||||
{
|
||||
if (T.TryParse(text, null, out var newValue))
|
||||
{
|
||||
@@ -102,7 +129,13 @@ public sealed class Settings : Window, IDisposable
|
||||
ImGuiUtils.TooltipWrapped(tooltip);
|
||||
}
|
||||
|
||||
private static void DrawOption(string label, string tooltip, string value, Action<string> setter, ref bool isDirty)
|
||||
private static void DrawOption(
|
||||
string label,
|
||||
string tooltip,
|
||||
string value,
|
||||
Action<string> setter,
|
||||
ref bool isDirty
|
||||
)
|
||||
{
|
||||
ImGui.SetNextItemWidth(OptionWidth);
|
||||
var text = value;
|
||||
@@ -118,7 +151,17 @@ public sealed class Settings : Window, IDisposable
|
||||
ImGuiUtils.TooltipWrapped(tooltip);
|
||||
}
|
||||
|
||||
private static void DrawOption<T>(string label, string tooltip, Func<T, string> getName, Func<T, string> getTooltip, T value, Action<T> setter, ref bool isDirty, params T[] excludedValues) where T : struct, Enum
|
||||
private static void DrawOption<T>(
|
||||
string label,
|
||||
string tooltip,
|
||||
Func<T, string> getName,
|
||||
Func<T, string> getTooltip,
|
||||
T value,
|
||||
Action<T> setter,
|
||||
ref bool isDirty,
|
||||
params T[] excludedValues
|
||||
)
|
||||
where T : struct, Enum
|
||||
{
|
||||
ImGui.SetNextItemWidth(OptionWidth);
|
||||
using (var combo = ImRaii.Combo(label, getName(value)))
|
||||
@@ -160,16 +203,16 @@ public sealed class Settings : Window, IDisposable
|
||||
{
|
||||
SolverAlgorithm.Oneshot => "Run through all iterations and pick the best macro",
|
||||
SolverAlgorithm.OneshotForked => "Oneshot, but using multiple solvers simultaneously",
|
||||
SolverAlgorithm.Stepwise => "Run through all iterations and pick the next best step, " +
|
||||
"and repeat using previous steps as a starting point",
|
||||
SolverAlgorithm.Stepwise => "Run through all iterations and pick the next best step, "
|
||||
+ "and repeat using previous steps as a starting point",
|
||||
SolverAlgorithm.StepwiseForked => "Stepwise, but using multiple solvers simultaneously",
|
||||
SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are " +
|
||||
"selected from the solvers, and each one is equally " +
|
||||
"used as a starting point",
|
||||
SolverAlgorithm.Raphael => "Finds the best solution, every time. This solver has " +
|
||||
"very different options compared to the rest, as it " +
|
||||
"is designed using an entirely different algorithm.",
|
||||
_ => "Unknown"
|
||||
SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are "
|
||||
+ "selected from the solvers, and each one is equally "
|
||||
+ "used as a starting point",
|
||||
SolverAlgorithm.Raphael => "Finds the best solution, every time. This solver has "
|
||||
+ "very different options compared to the rest, as it "
|
||||
+ "is designed using an entirely different algorithm.",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
private static string GetCopyTypeName(MacroCopyConfiguration.CopyType type) =>
|
||||
@@ -185,12 +228,16 @@ public sealed class Settings : Window, IDisposable
|
||||
private static string GetCopyTypeTooltip(MacroCopyConfiguration.CopyType type) =>
|
||||
type switch
|
||||
{
|
||||
MacroCopyConfiguration.CopyType.OpenWindow => "Open a dedicated window with all macros being copied. " +
|
||||
"Copy, view, and choose at your own leisure.",
|
||||
MacroCopyConfiguration.CopyType.CopyToMacro => "Copy directly to the game's macro system.",
|
||||
MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to your clipboard. Macros are separated by a blank line.",
|
||||
MacroCopyConfiguration.CopyType.CopyToMacroMate => "Copy directly to a Macro Mate macro. Requires the Macro Mate plugin.",
|
||||
_ => "Unknown"
|
||||
MacroCopyConfiguration.CopyType.OpenWindow =>
|
||||
"Open a dedicated window with all macros being copied. "
|
||||
+ "Copy, view, and choose at your own leisure.",
|
||||
MacroCopyConfiguration.CopyType.CopyToMacro =>
|
||||
"Copy directly to the game's macro system.",
|
||||
MacroCopyConfiguration.CopyType.CopyToClipboard =>
|
||||
"Copy to your clipboard. Macros are separated by a blank line.",
|
||||
MacroCopyConfiguration.CopyType.CopyToMacroMate =>
|
||||
"Copy directly to a Macro Mate macro. Requires the Macro Mate plugin.",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
private static string GetProgressBarTypeName(Configuration.ProgressBarType type) =>
|
||||
@@ -207,8 +254,9 @@ public sealed class Settings : Window, IDisposable
|
||||
{
|
||||
Configuration.ProgressBarType.Colorful => "Colorful, rainbow colors",
|
||||
Configuration.ProgressBarType.Simple => "Simple, grayscale colors",
|
||||
Configuration.ProgressBarType.None => "No progress bar; only percent completion is shown",
|
||||
_ => "Unknown"
|
||||
Configuration.ProgressBarType.None =>
|
||||
"No progress bar; only percent completion is shown",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
public override void Draw()
|
||||
@@ -238,9 +286,9 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Enable Synthesis Helper",
|
||||
"Adds a helper next to your synthesis window to help solve for the best craft. " +
|
||||
"Extremely useful for expert recipes, where the condition can greatly affect " +
|
||||
"which actions you take.",
|
||||
"Adds a helper next to your synthesis window to help solve for the best craft. "
|
||||
+ "Extremely useful for expert recipes, where the condition can greatly affect "
|
||||
+ "which actions you take.",
|
||||
Config.EnableSynthHelper,
|
||||
v => Config.EnableSynthHelper = v,
|
||||
ref isDirty
|
||||
@@ -248,9 +296,9 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Show Only One Macro Stat in Crafting Log",
|
||||
"Only one stat will be shown for a macro. If a craft will be finished, quality " +
|
||||
"is shown. Otherwise, progress is shown. Durability and remaining CP will be " +
|
||||
"hidden.",
|
||||
"Only one stat will be shown for a macro. If a craft will be finished, quality "
|
||||
+ "is shown. Otherwise, progress is shown. Durability and remaining CP will be "
|
||||
+ "hidden.",
|
||||
Config.ShowOptimalMacroStat,
|
||||
v => Config.ShowOptimalMacroStat = v,
|
||||
ref isDirty
|
||||
@@ -258,8 +306,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Check For Delineations",
|
||||
"Your inventory will be checked to ensure that you have delineations available " +
|
||||
"before suggesting any specialist actions.",
|
||||
"Your inventory will be checked to ensure that you have delineations available "
|
||||
+ "before suggesting any specialist actions.",
|
||||
Config.CheckDelineations,
|
||||
v => Config.CheckDelineations = v,
|
||||
ref isDirty
|
||||
@@ -267,9 +315,9 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Reliability Trial Count",
|
||||
"When testing for reliability of a macro in the editor, this many trials will be " +
|
||||
"run. You should set this value to at least 100 to get a reliable spread of data. " +
|
||||
"If it's too low, you may not find an outlier, and the average might be skewed.",
|
||||
"When testing for reliability of a macro in the editor, this many trials will be "
|
||||
+ "run. You should set this value to at least 100 to get a reliable spread of data. "
|
||||
+ "If it's too low, you may not find an outlier, and the average might be skewed.",
|
||||
Config.ReliabilitySimulationCount,
|
||||
5,
|
||||
5000,
|
||||
@@ -301,8 +349,13 @@ public sealed class Settings : Window, IDisposable
|
||||
ref isDirty
|
||||
);
|
||||
|
||||
if (Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacroMate &&
|
||||
!Service.PluginInterface.InstalledPlugins.Any(p => p.IsLoaded && string.Equals(p.InternalName, "MacroMate", StringComparison.Ordinal)))
|
||||
if (
|
||||
Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacroMate
|
||||
&& !Service.PluginInterface.InstalledPlugins.Any(p =>
|
||||
p.IsLoaded
|
||||
&& string.Equals(p.InternalName, "MacroMate", StringComparison.Ordinal)
|
||||
)
|
||||
)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange))
|
||||
@@ -326,8 +379,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Copy to Shared Macros",
|
||||
"Copy to the shared macros tab. Leaving this unchecked copies to the " +
|
||||
"individual tab.",
|
||||
"Copy to the shared macros tab. Leaving this unchecked copies to the "
|
||||
+ "individual tab.",
|
||||
Config.MacroCopy.SharedMacro,
|
||||
v => Config.MacroCopy.SharedMacro = v,
|
||||
ref isDirty
|
||||
@@ -335,20 +388,22 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Macro Number",
|
||||
"The # of the macro to being copying to. Subsequent macros will be " +
|
||||
"copied relative to this macro.",
|
||||
"The # of the macro to being copying to. Subsequent macros will be "
|
||||
+ "copied relative to this macro.",
|
||||
Config.MacroCopy.StartMacroIdx,
|
||||
0, 99,
|
||||
0,
|
||||
99,
|
||||
v => Config.MacroCopy.StartMacroIdx = v,
|
||||
ref isDirty
|
||||
);
|
||||
|
||||
DrawOption(
|
||||
"Max Macro Copy Count",
|
||||
"The maximum number of macros to be copied. Any more and a window is " +
|
||||
"displayed with the rest of them.",
|
||||
"The maximum number of macros to be copied. Any more and a window is "
|
||||
+ "displayed with the rest of them.",
|
||||
Config.MacroCopy.MaxMacroCount,
|
||||
1, 99,
|
||||
1,
|
||||
99,
|
||||
v => Config.MacroCopy.MaxMacroCount = v,
|
||||
ref isDirty
|
||||
);
|
||||
@@ -384,15 +439,20 @@ public sealed class Settings : Window, IDisposable
|
||||
{
|
||||
DrawOption(
|
||||
"Use Macro Chain",
|
||||
"Replaces the last step with /nextmacro to run the next macro " +
|
||||
"automatically. Overrides the Intermediate Notification Sound.",
|
||||
"Replaces the last step with /nextmacro to run the next macro "
|
||||
+ "automatically. Overrides the Intermediate Notification Sound.",
|
||||
Config.MacroCopy.UseNextMacro,
|
||||
v => Config.MacroCopy.UseNextMacro = v,
|
||||
ref isDirty
|
||||
);
|
||||
|
||||
if (Config.MacroCopy.UseNextMacro &&
|
||||
!Service.PluginInterface.InstalledPlugins.Any(p => p.IsLoaded && string.Equals(p.InternalName, "MacroChain", StringComparison.Ordinal)))
|
||||
if (
|
||||
Config.MacroCopy.UseNextMacro
|
||||
&& !Service.PluginInterface.InstalledPlugins.Any(p =>
|
||||
p.IsLoaded
|
||||
&& string.Equals(p.InternalName, "MacroChain", StringComparison.Ordinal)
|
||||
)
|
||||
)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange))
|
||||
@@ -407,8 +467,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Add Macro Lock",
|
||||
"Adds /mlock to the beginning of every macro. Prevents other " +
|
||||
"macros from being run.",
|
||||
"Adds /mlock to the beginning of every macro. Prevents other "
|
||||
+ "macros from being run.",
|
||||
Config.MacroCopy.UseMacroLock,
|
||||
v => Config.MacroCopy.UseMacroLock = v,
|
||||
ref isDirty
|
||||
@@ -424,12 +484,18 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
if (Config.MacroCopy.AddNotification)
|
||||
{
|
||||
if ((Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !Config.MacroCopy.CombineMacro) && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
|
||||
if (
|
||||
(
|
||||
Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro
|
||||
|| !Config.MacroCopy.CombineMacro
|
||||
)
|
||||
&& Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate
|
||||
)
|
||||
{
|
||||
DrawOption(
|
||||
"Force Notification",
|
||||
"Prioritize always having a notification sound at the end of " +
|
||||
"every macro. Keeping this off prevents macros with only 1 action.",
|
||||
"Prioritize always having a notification sound at the end of "
|
||||
+ "every macro. Keeping this off prevents macros with only 1 action.",
|
||||
Config.MacroCopy.ForceNotification,
|
||||
v => Config.MacroCopy.ForceNotification = v,
|
||||
ref isDirty
|
||||
@@ -446,14 +512,18 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
if (Config.MacroCopy.AddNotificationSound)
|
||||
{
|
||||
if (!Config.MacroCopy.UseNextMacro && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
|
||||
if (
|
||||
!Config.MacroCopy.UseNextMacro
|
||||
&& Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate
|
||||
)
|
||||
{
|
||||
DrawOption(
|
||||
"Intermediate Notification Sound",
|
||||
"Ending notification sound for an intermediary macro.\n" +
|
||||
"Uses <se.#>",
|
||||
"Ending notification sound for an intermediary macro.\n"
|
||||
+ "Uses <se.#>",
|
||||
Config.MacroCopy.IntermediateNotificationSound,
|
||||
1, 16,
|
||||
1,
|
||||
16,
|
||||
v =>
|
||||
{
|
||||
Config.MacroCopy.IntermediateNotificationSound = v;
|
||||
@@ -465,10 +535,10 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Final Notification Sound",
|
||||
"Ending notification sound for the final macro.\n" +
|
||||
"Uses <se.#>",
|
||||
"Ending notification sound for the final macro.\n" + "Uses <se.#>",
|
||||
Config.MacroCopy.EndNotificationSound,
|
||||
1, 16,
|
||||
1,
|
||||
16,
|
||||
v =>
|
||||
{
|
||||
Config.MacroCopy.EndNotificationSound = v;
|
||||
@@ -506,7 +576,12 @@ public sealed class Settings : Window, IDisposable
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
private static void DrawSolverConfig(ref SolverConfig configRef, SolverConfig defaultConfig, bool disableOptimal, out bool isDirty)
|
||||
private static void DrawSolverConfig(
|
||||
ref SolverConfig configRef,
|
||||
SolverConfig defaultConfig,
|
||||
bool disableOptimal,
|
||||
out bool isDirty
|
||||
)
|
||||
{
|
||||
isDirty = false;
|
||||
|
||||
@@ -522,10 +597,10 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Algorithm",
|
||||
"The algorithm to use when solving for a macro. Different " +
|
||||
"algorithms provide different pros and cons for using them. " +
|
||||
"By far, the Optimal and Stepwise Genetic algorithms provide " +
|
||||
"the best results, especially for very difficult crafts.",
|
||||
"The algorithm to use when solving for a macro. Different "
|
||||
+ "algorithms provide different pros and cons for using them. "
|
||||
+ "By far, the Optimal and Stepwise Genetic algorithms provide "
|
||||
+ "the best results, especially for very difficult crafts.",
|
||||
GetAlgorithmName,
|
||||
GetAlgorithmTooltip,
|
||||
config.Algorithm,
|
||||
@@ -534,15 +609,25 @@ public sealed class Settings : Window, IDisposable
|
||||
disableOptimal ? [SolverAlgorithm.Raphael] : []
|
||||
);
|
||||
|
||||
using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic or SolverAlgorithm.Raphael)))
|
||||
using (
|
||||
ImRaii.Disabled(
|
||||
config.Algorithm
|
||||
is not (
|
||||
SolverAlgorithm.OneshotForked
|
||||
or SolverAlgorithm.StepwiseForked
|
||||
or SolverAlgorithm.StepwiseGenetic
|
||||
or SolverAlgorithm.Raphael
|
||||
)
|
||||
)
|
||||
)
|
||||
DrawOption(
|
||||
"Max Core Count",
|
||||
"The number of cores to use when solving. You should use as many " +
|
||||
"as you can. If it's too high, it will have an effect on your gameplay " +
|
||||
$"experience. A good estimate would be 1 or 2 cores less than your " +
|
||||
$"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate " +
|
||||
$"for any other tasks you have in the background, if you have any.\n" +
|
||||
"(Only used in the Forked, Genetic, and Optimal algorithms)",
|
||||
"The number of cores to use when solving. You should use as many "
|
||||
+ "as you can. If it's too high, it will have an effect on your gameplay "
|
||||
+ $"experience. A good estimate would be 1 or 2 cores less than your "
|
||||
+ $"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate "
|
||||
+ $"for any other tasks you have in the background, if you have any.\n"
|
||||
+ "(Only used in the Forked, Genetic, and Optimal algorithms)",
|
||||
config.MaxThreadCount,
|
||||
1,
|
||||
Environment.ProcessorCount,
|
||||
@@ -554,10 +639,10 @@ public sealed class Settings : Window, IDisposable
|
||||
{
|
||||
DrawOption(
|
||||
"Target Iterations",
|
||||
"The total number of iterations to run per crafting step. " +
|
||||
"Higher values require more computational power. Higher values " +
|
||||
"also may decrease variance, so other values should be tweaked " +
|
||||
"as necessary to get a more favorable outcome.",
|
||||
"The total number of iterations to run per crafting step. "
|
||||
+ "Higher values require more computational power. Higher values "
|
||||
+ "also may decrease variance, so other values should be tweaked "
|
||||
+ "as necessary to get a more favorable outcome.",
|
||||
config.Iterations,
|
||||
1000,
|
||||
1000000,
|
||||
@@ -567,11 +652,11 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Max Iterations",
|
||||
"The solver may go about the target iteration value if the craft " +
|
||||
"is sufficiently difficult, and it wasn't able to find any way to " +
|
||||
"complete it yet. In rare cases, the solver might go on for a very " +
|
||||
"long time. This maximum is here to prevent the solver from stealing " +
|
||||
"all your RAM.",
|
||||
"The solver may go about the target iteration value if the craft "
|
||||
+ "is sufficiently difficult, and it wasn't able to find any way to "
|
||||
+ "complete it yet. In rare cases, the solver might go on for a very "
|
||||
+ "long time. This maximum is here to prevent the solver from stealing "
|
||||
+ "all your RAM.",
|
||||
config.MaxIterations,
|
||||
config.Iterations,
|
||||
5000000,
|
||||
@@ -581,11 +666,11 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Max Step Count",
|
||||
"The maximum number of crafting steps; this is generally the only " +
|
||||
"setting you should change, and it should be set to around 5 steps " +
|
||||
"more than what you'd expect. If this value is too low, the solver " +
|
||||
"won't learn much per iteration; too high and it will waste time " +
|
||||
"on useless extra steps.",
|
||||
"The maximum number of crafting steps; this is generally the only "
|
||||
+ "setting you should change, and it should be set to around 5 steps "
|
||||
+ "more than what you'd expect. If this value is too low, the solver "
|
||||
+ "won't learn much per iteration; too high and it will waste time "
|
||||
+ "on useless extra steps.",
|
||||
config.MaxStepCount,
|
||||
1,
|
||||
100,
|
||||
@@ -595,9 +680,9 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Exploration Constant",
|
||||
"A constant that decides how often the solver will explore new, " +
|
||||
"possibly good paths. If this value is too high, " +
|
||||
"moves will mostly be decided at random.",
|
||||
"A constant that decides how often the solver will explore new, "
|
||||
+ "possibly good paths. If this value is too high, "
|
||||
+ "moves will mostly be decided at random.",
|
||||
config.ExplorationConstant,
|
||||
0,
|
||||
10,
|
||||
@@ -607,10 +692,10 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Score Weighting Constant",
|
||||
"A constant ranging from 0 to 1 that configures how the solver " +
|
||||
"scores and picks paths to travel to next. A value of 0 means " +
|
||||
"actions will be chosen based on their average outcome, whereas " +
|
||||
"1 uses their best outcome achieved so far.",
|
||||
"A constant ranging from 0 to 1 that configures how the solver "
|
||||
+ "scores and picks paths to travel to next. A value of 0 means "
|
||||
+ "actions will be chosen based on their average outcome, whereas "
|
||||
+ "1 uses their best outcome achieved so far.",
|
||||
config.MaxScoreWeightingConstant,
|
||||
0,
|
||||
1,
|
||||
@@ -618,16 +703,25 @@ public sealed class Settings : Window, IDisposable
|
||||
ref isDirty
|
||||
);
|
||||
|
||||
using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic)))
|
||||
using (
|
||||
ImRaii.Disabled(
|
||||
config.Algorithm
|
||||
is not (
|
||||
SolverAlgorithm.OneshotForked
|
||||
or SolverAlgorithm.StepwiseForked
|
||||
or SolverAlgorithm.StepwiseGenetic
|
||||
)
|
||||
)
|
||||
)
|
||||
DrawOption(
|
||||
"Fork Count",
|
||||
"Split the number of iterations across different solvers. In general, " +
|
||||
"you should increase this value to at least the number of cores in " +
|
||||
$"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. " +
|
||||
"The higher the number, the more chance you have of finding a " +
|
||||
"better local maximum; this concept similar but not equivalent " +
|
||||
"to the exploration constant.\n" +
|
||||
"(Only used in the Forked and Genetic algorithms)",
|
||||
"Split the number of iterations across different solvers. In general, "
|
||||
+ "you should increase this value to at least the number of cores in "
|
||||
+ $"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. "
|
||||
+ "The higher the number, the more chance you have of finding a "
|
||||
+ "better local maximum; this concept similar but not equivalent "
|
||||
+ "to the exploration constant.\n"
|
||||
+ "(Only used in the Forked and Genetic algorithms)",
|
||||
config.ForkCount,
|
||||
1,
|
||||
500,
|
||||
@@ -638,10 +732,10 @@ public sealed class Settings : Window, IDisposable
|
||||
using (ImRaii.Disabled(config.Algorithm is not SolverAlgorithm.StepwiseGenetic))
|
||||
DrawOption(
|
||||
"Elitist Action Count",
|
||||
"On every craft step, pick this many top solutions and use them as " +
|
||||
"the input for the next craft step. For best results, use Fork Count / 2 " +
|
||||
"and add about 1 or 2 more if needed.\n" +
|
||||
"(Only used in the Stepwise Genetic algorithm)",
|
||||
"On every craft step, pick this many top solutions and use them as "
|
||||
+ "the input for the next craft step. For best results, use Fork Count / 2 "
|
||||
+ "and add about 1 or 2 more if needed.\n"
|
||||
+ "(Only used in the Stepwise Genetic algorithm)",
|
||||
config.FurcatedActionCount,
|
||||
1,
|
||||
500,
|
||||
@@ -653,16 +747,16 @@ public sealed class Settings : Window, IDisposable
|
||||
{
|
||||
DrawOption(
|
||||
"Backload Progress",
|
||||
"Speeds up solve times. Backloads all Progress " +
|
||||
"actions to the end of the rotation.",
|
||||
"Speeds up solve times. Backloads all Progress "
|
||||
+ "actions to the end of the rotation.",
|
||||
config.BackloadProgress,
|
||||
v => config = config with { BackloadProgress = v },
|
||||
ref isDirty
|
||||
);
|
||||
DrawOption(
|
||||
"Ensure Reliability",
|
||||
"Find a rotation that can reach the target quality no matter " +
|
||||
"how unlucky the random conditions are.",
|
||||
"Find a rotation that can reach the target quality no matter "
|
||||
+ "how unlucky the random conditions are.",
|
||||
config.Adversarial,
|
||||
v => config = config with { Adversarial = v },
|
||||
ref isDirty
|
||||
@@ -677,7 +771,9 @@ public sealed class Settings : Window, IDisposable
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.TooltipWrapped("\"Ensure Reliability\" uses a lot more memory and can significantly increase solve times.");
|
||||
ImGuiUtils.TooltipWrapped(
|
||||
"\"Ensure Reliability\" uses a lot more memory and can significantly increase solve times."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -703,9 +799,9 @@ public sealed class Settings : Window, IDisposable
|
||||
{
|
||||
DrawOption(
|
||||
"Max Rollout Step Count",
|
||||
"The maximum number of crafting steps every iteration can consider. " +
|
||||
"Decreasing this value can have unintended side effects. Only change " +
|
||||
"this value if you absolutely know what you're doing.",
|
||||
"The maximum number of crafting steps every iteration can consider. "
|
||||
+ "Decreasing this value can have unintended side effects. Only change "
|
||||
+ "this value if you absolutely know what you're doing.",
|
||||
config.MaxRolloutStepCount,
|
||||
1,
|
||||
50,
|
||||
@@ -715,9 +811,9 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Strict Actions",
|
||||
"When finding the next possible actions to execute, use a heuristic " +
|
||||
"to restrict which actions to attempt taking. This results in a much " +
|
||||
"better macro at the cost of not finding an extremely creative one.",
|
||||
"When finding the next possible actions to execute, use a heuristic "
|
||||
+ "to restrict which actions to attempt taking. This results in a much "
|
||||
+ "better macro at the cost of not finding an extremely creative one.",
|
||||
config.StrictActions,
|
||||
v => config = config with { StrictActions = v },
|
||||
ref isDirty
|
||||
@@ -771,8 +867,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Steps",
|
||||
"Amount of weight to give to the craft's number of steps. The lower " +
|
||||
"the step count, the higher the score.",
|
||||
"Amount of weight to give to the craft's number of steps. The lower "
|
||||
+ "the step count, the higher the score.",
|
||||
config.ScoreSteps,
|
||||
0,
|
||||
100,
|
||||
@@ -786,7 +882,11 @@ public sealed class Settings : Window, IDisposable
|
||||
configRef = config;
|
||||
}
|
||||
|
||||
private static void DrawActionPool(ref ActionType[] actionPool, float poolWidth, out bool isDirty)
|
||||
private static void DrawActionPool(
|
||||
ref ActionType[] actionPool,
|
||||
float poolWidth,
|
||||
out bool isDirty
|
||||
)
|
||||
{
|
||||
isDirty = false;
|
||||
|
||||
@@ -799,14 +899,21 @@ public sealed class Settings : Window, IDisposable
|
||||
using var _color = ImRaii.PushColor(ImGuiCol.Button, Vector4.Zero);
|
||||
using var _color3 = ImRaii.PushColor(ImGuiCol.ButtonHovered, Vector4.Zero);
|
||||
using var _color2 = ImRaii.PushColor(ImGuiCol.ButtonActive, Vector4.Zero);
|
||||
using var _alpha = ImRaii.PushStyle(ImGuiStyleVar.DisabledAlpha, ImGui.GetStyle().DisabledAlpha * .5f);
|
||||
using var _alpha = ImRaii.PushStyle(
|
||||
ImGuiStyleVar.DisabledAlpha,
|
||||
ImGui.GetStyle().DisabledAlpha * .5f
|
||||
);
|
||||
foreach (var category in Enum.GetValues<ActionCategory>())
|
||||
{
|
||||
if (category == ActionCategory.Combo)
|
||||
continue;
|
||||
|
||||
var actions = category.GetActions();
|
||||
using var panel = ImRaii2.GroupPanel(category.GetDisplayName(), poolWidth, out var availSpace);
|
||||
using var panel = ImRaii2.GroupPanel(
|
||||
category.GetDisplayName(),
|
||||
poolWidth,
|
||||
out var availSpace
|
||||
);
|
||||
var itemsPerRow = (int)MathF.Floor((availSpace + spacing) / (imageSize + spacing));
|
||||
var itemCount = actions.Count;
|
||||
var iterCount = (int)(Math.Ceiling((float)itemCount / itemsPerRow) * itemsPerRow);
|
||||
@@ -827,7 +934,17 @@ public sealed class Settings : Window, IDisposable
|
||||
iconTint = new(1, 1f, .5f, 1);
|
||||
else if (isRisky)
|
||||
iconTint = new(1, .5f, .5f, 1);
|
||||
if (ImGui.ImageButton(actions[i].GetIcon(recipeData.ClassJob).Handle, new(imageSize), default, Vector2.One, 0, default, iconTint))
|
||||
if (
|
||||
ImGui.ImageButton(
|
||||
actions[i].GetIcon(recipeData.ClassJob).Handle,
|
||||
new(imageSize),
|
||||
default,
|
||||
Vector2.One,
|
||||
0,
|
||||
default,
|
||||
iconTint
|
||||
)
|
||||
)
|
||||
{
|
||||
isDirty = true;
|
||||
if (isEnabled)
|
||||
@@ -841,16 +958,18 @@ public sealed class Settings : Window, IDisposable
|
||||
s.AppendLine(actions[i].GetName(recipeData.ClassJob));
|
||||
if (isInefficient)
|
||||
s.AppendLine(
|
||||
"Not recommended. This action may be randomly used in a " +
|
||||
"detrimental way to the rest of the craft. Always use " +
|
||||
"your best judgement if enabling this action.");
|
||||
"Not recommended. This action may be randomly used in a "
|
||||
+ "detrimental way to the rest of the craft. Always use "
|
||||
+ "your best judgement if enabling this action."
|
||||
);
|
||||
if (isRisky)
|
||||
s.AppendLine(
|
||||
"Useless; the solver currently doesn't take any risks in " +
|
||||
"its crafts. It only takes steps that have a 100% chance of " +
|
||||
"succeeding. If you want have a moment where you want to take " +
|
||||
"risks in your craft (like in expert recipes), don't rely " +
|
||||
"on the solver during that time.");
|
||||
"Useless; the solver currently doesn't take any risks in "
|
||||
+ "its crafts. It only takes steps that have a 100% chance of "
|
||||
+ "succeeding. If you want have a moment where you want to take "
|
||||
+ "risks in your craft (like in expert recipes), don't rely "
|
||||
+ "on the solver during that time."
|
||||
);
|
||||
ImGuiUtils.TooltipWrapped(s.ToString());
|
||||
}
|
||||
}
|
||||
@@ -912,8 +1031,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Pin Helper Window",
|
||||
"Pins the helper window to the right of your crafting log. Disabling this will " +
|
||||
"allow you to move it around.",
|
||||
"Pins the helper window to the right of your crafting log. Disabling this will "
|
||||
+ "allow you to move it around.",
|
||||
Config.PinRecipeNoteToWindow,
|
||||
v => Config.PinRecipeNoteToWindow = v,
|
||||
ref isDirty
|
||||
@@ -921,8 +1040,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Always Collapse Helper Window",
|
||||
"Enabling this will cause the Helper Window to be collapsed whenever you start " +
|
||||
"a new craft, preventing the solver from running automatically.",
|
||||
"Enabling this will cause the Helper Window to be collapsed whenever you start "
|
||||
+ "a new craft, preventing the solver from running automatically.",
|
||||
Config.CollapseSynthHelper,
|
||||
v => Config.CollapseSynthHelper = v,
|
||||
ref isDirty
|
||||
@@ -930,11 +1049,11 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Automatically Suggest Macro",
|
||||
"(Can cause frame drops!) When navigating to a new recipe or changing your gear " +
|
||||
"stats, automatically suggest a new macro (equivalent to clicking \"Generate\" " +
|
||||
"in the Macro Editor). This can cause harsh frame drops on some computers or " +
|
||||
"recipes when underleveled while navigating the crafting log. Turning this off " +
|
||||
"provides a button to allow you to manually suggest a macro only when you need it.",
|
||||
"(Can cause frame drops!) When navigating to a new recipe or changing your gear "
|
||||
+ "stats, automatically suggest a new macro (equivalent to clicking \"Generate\" "
|
||||
+ "in the Macro Editor). This can cause harsh frame drops on some computers or "
|
||||
+ "recipes when underleveled while navigating the crafting log. Turning this off "
|
||||
+ "provides a button to allow you to manually suggest a macro only when you need it.",
|
||||
Config.SuggestMacroAutomatically,
|
||||
v => Config.SuggestMacroAutomatically = v,
|
||||
ref isDirty
|
||||
@@ -942,10 +1061,10 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Enable Community Macros",
|
||||
"Use FFXIV Teamcraft's community rotations to search for and find the best possible " +
|
||||
"crowd-sourced macro for your craft. This sends a request to their servers to retrieve " +
|
||||
"a list of macros that apply to your craft's rlvl. Requests are only sent once per rlvl " +
|
||||
"and are always cached to reduce server load.",
|
||||
"Use FFXIV Teamcraft's community rotations to search for and find the best possible "
|
||||
+ "crowd-sourced macro for your craft. This sends a request to their servers to retrieve "
|
||||
+ "a list of macros that apply to your craft's rlvl. Requests are only sent once per rlvl "
|
||||
+ "and are always cached to reduce server load.",
|
||||
Config.ShowCommunityMacros,
|
||||
v => Config.ShowCommunityMacros = v,
|
||||
ref isDirty
|
||||
@@ -955,9 +1074,9 @@ public sealed class Settings : Window, IDisposable
|
||||
{
|
||||
DrawOption(
|
||||
"Automatically Search for Community Macro",
|
||||
"When navigating to a new recipe or changing your gear stats, automatically search " +
|
||||
"online for a new community macro.\n" +
|
||||
"This is turned off by default so you don't hammer their servers :)",
|
||||
"When navigating to a new recipe or changing your gear stats, automatically search "
|
||||
+ "online for a new community macro.\n"
|
||||
+ "This is turned off by default so you don't hammer their servers :)",
|
||||
Config.SearchCommunityMacroAutomatically,
|
||||
v => Config.SearchCommunityMacroAutomatically = v,
|
||||
ref isDirty
|
||||
@@ -969,7 +1088,12 @@ public sealed class Settings : Window, IDisposable
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
var solverConfig = Config.RecipeNoteSolverConfig;
|
||||
DrawSolverConfig(ref solverConfig, SolverConfig.RecipeNoteDefault, false, out var isSolverDirty);
|
||||
DrawSolverConfig(
|
||||
ref solverConfig,
|
||||
SolverConfig.RecipeNoteDefault,
|
||||
false,
|
||||
out var isSolverDirty
|
||||
);
|
||||
if (isSolverDirty)
|
||||
{
|
||||
Config.RecipeNoteSolverConfig = solverConfig;
|
||||
@@ -991,7 +1115,12 @@ public sealed class Settings : Window, IDisposable
|
||||
var isDirty = false;
|
||||
|
||||
var solverConfig = Config.EditorSolverConfig;
|
||||
DrawSolverConfig(ref solverConfig, SolverConfig.EditorDefault, false, out var isSolverDirty);
|
||||
DrawSolverConfig(
|
||||
ref solverConfig,
|
||||
SolverConfig.EditorDefault,
|
||||
false,
|
||||
out var isSolverDirty
|
||||
);
|
||||
if (isSolverDirty)
|
||||
{
|
||||
Config.EditorSolverConfig = solverConfig;
|
||||
@@ -1014,8 +1143,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Pin Helper Window",
|
||||
"Pins the synthesis helper to the right of your synthesis window. Disabling this will " +
|
||||
"allow you to move it around.",
|
||||
"Pins the synthesis helper to the right of your synthesis window. Disabling this will "
|
||||
+ "allow you to move it around.",
|
||||
Config.PinSynthHelperToWindow,
|
||||
v => Config.PinSynthHelperToWindow = v,
|
||||
ref isDirty
|
||||
@@ -1031,9 +1160,9 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Simulate Only First Step",
|
||||
"Only the first step is simulated by default. You can still " +
|
||||
"hover over the other steps to view their outcomes, but the " +
|
||||
"reliability trials (when hovering over the macro stats) are hidden.",
|
||||
"Only the first step is simulated by default. You can still "
|
||||
+ "hover over the other steps to view their outcomes, but the "
|
||||
+ "reliability trials (when hovering over the macro stats) are hidden.",
|
||||
Config.SynthHelperDisplayOnlyFirstStep,
|
||||
v => Config.SynthHelperDisplayOnlyFirstStep = v,
|
||||
ref isDirty
|
||||
@@ -1041,9 +1170,9 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Draw Ability Ants",
|
||||
"Turns your hotbar into a whack-a-mole game! Draws ants for " +
|
||||
"the next action that should be executed. Also disables ants " +
|
||||
"for things like combo actions and condition procs.",
|
||||
"Turns your hotbar into a whack-a-mole game! Draws ants for "
|
||||
+ "the next action that should be executed. Also disables ants "
|
||||
+ "for things like combo actions and condition procs.",
|
||||
Config.SynthHelperAbilityAnts,
|
||||
v => Config.SynthHelperAbilityAnts = v,
|
||||
ref isDirty
|
||||
@@ -1051,8 +1180,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Solver Step Count",
|
||||
"The minimum number of future steps to solve for during an in-game craft. " +
|
||||
"The solver may still give more than this amount if it's at no cost to you.",
|
||||
"The minimum number of future steps to solve for during an in-game craft. "
|
||||
+ "The solver may still give more than this amount if it's at no cost to you.",
|
||||
Config.SynthHelperStepCount,
|
||||
1,
|
||||
100,
|
||||
@@ -1062,8 +1191,8 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
DrawOption(
|
||||
"Max Step Display Count",
|
||||
"Enforces a maximum number of steps to display in the synth helper to " +
|
||||
"get rid of clutter.",
|
||||
"Enforces a maximum number of steps to display in the synth helper to "
|
||||
+ "get rid of clutter.",
|
||||
Config.SynthHelperMaxDisplayCount,
|
||||
Config.SynthHelperStepCount,
|
||||
100,
|
||||
@@ -1076,7 +1205,12 @@ public sealed class Settings : Window, IDisposable
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
var solverConfig = Config.SynthHelperSolverConfig;
|
||||
DrawSolverConfig(ref solverConfig, SolverConfig.SynthHelperDefault, true, out var isSolverDirty);
|
||||
DrawSolverConfig(
|
||||
ref solverConfig,
|
||||
SolverConfig.SynthHelperDefault,
|
||||
true,
|
||||
out var isSolverDirty
|
||||
);
|
||||
if (isSolverDirty)
|
||||
{
|
||||
Config.SynthHelperSolverConfig = solverConfig;
|
||||
@@ -1109,18 +1243,33 @@ public sealed class Settings : Window, IDisposable
|
||||
ImGui.Image(icon.Handle, iconDim);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtils.AlignMiddle(new(float.PositiveInfinity, HeaderFont.GetFontSize() + SubheaderFont.GetFontSize() + ImGui.GetFontSize() * 3 + ImGui.GetStyle().ItemSpacing.Y * 4), new(0, iconDim.Y));
|
||||
ImGuiUtils.AlignMiddle(
|
||||
new(
|
||||
float.PositiveInfinity,
|
||||
HeaderFont.GetFontSize()
|
||||
+ SubheaderFont.GetFontSize()
|
||||
+ ImGui.GetFontSize() * 3
|
||||
+ ImGui.GetStyle().ItemSpacing.Y * 4
|
||||
),
|
||||
new(0, iconDim.Y)
|
||||
);
|
||||
|
||||
using (HeaderFont.Push())
|
||||
{
|
||||
ImGuiUtils.AlignCentered(ImGui.CalcTextSize("Forgeimizer").X);
|
||||
ImGuiUtils.Hyperlink("Forgeimizer", "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer", false);
|
||||
ImGuiUtils.Hyperlink(
|
||||
"Forgeimizer",
|
||||
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
using (SubheaderFont.Push())
|
||||
ImGuiUtils.TextCentered($"v{plugin.Version} {plugin.BuildConfiguration}");
|
||||
|
||||
ImGuiUtils.AlignCentered(ImGui.CalcTextSize($"By {plugin.Author} (WorkingRobot)").X);
|
||||
ImGuiUtils.AlignCentered(
|
||||
ImGui.CalcTextSize($"By {plugin.Author} (WorkingRobot)").X
|
||||
);
|
||||
ImGui.TextUnformatted($"By {plugin.Author} (");
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGuiUtils.Hyperlink("WorkingRobot", "https://github.com/WorkingRobot");
|
||||
@@ -1178,11 +1327,17 @@ public sealed class Settings : Window, IDisposable
|
||||
|
||||
ImGuiUtils.TextWrappedTo("Thank you to ");
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGuiUtils.Hyperlink("this", "https://dke.maastrichtuniversity.nl/m.winands/documents/multithreadedMCTS2.pdf");
|
||||
ImGuiUtils.Hyperlink(
|
||||
"this",
|
||||
"https://dke.maastrichtuniversity.nl/m.winands/documents/multithreadedMCTS2.pdf"
|
||||
);
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGuiUtils.TextWrappedTo(", ");
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGuiUtils.Hyperlink("this", "https://liacs.leidenuniv.nl/~plaata1/papers/paper_ICAART18.pdf");
|
||||
ImGuiUtils.Hyperlink(
|
||||
"this",
|
||||
"https://liacs.leidenuniv.nl/~plaata1/papers/paper_ICAART18.pdf"
|
||||
);
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGuiUtils.TextWrappedTo(", and ");
|
||||
ImGui.SameLine(0, 0);
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Craftimizer.Plugin;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
@@ -15,12 +21,6 @@ using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
||||
using Sim = Craftimizer.Simulator.Simulator;
|
||||
using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom;
|
||||
@@ -29,12 +29,11 @@ namespace Craftimizer.Windows;
|
||||
|
||||
public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
{
|
||||
private const ImGuiWindowFlags WindowFlagsPinned = WindowFlagsFloating
|
||||
| ImGuiWindowFlags.NoSavedSettings;
|
||||
private const ImGuiWindowFlags WindowFlagsPinned =
|
||||
WindowFlagsFloating | ImGuiWindowFlags.NoSavedSettings;
|
||||
|
||||
private const ImGuiWindowFlags WindowFlagsFloating =
|
||||
ImGuiWindowFlags.AlwaysAutoResize
|
||||
| ImGuiWindowFlags.NoFocusOnAppearing;
|
||||
ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoFocusOnAppearing;
|
||||
|
||||
private const string WindowNamePinned = "Craftimizer Synthesis Helper###CraftimizerSynthHelper";
|
||||
private const string WindowNameFloating = $"{WindowNamePinned}Floating";
|
||||
@@ -69,9 +68,12 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
|
||||
private IFontHandle AxisFont { get; }
|
||||
|
||||
public SynthHelper() : base(WindowNamePinned)
|
||||
public SynthHelper()
|
||||
: base(WindowNamePinned)
|
||||
{
|
||||
AxisFont = Service.PluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(new(GameFontFamilyAndSize.Axis14));
|
||||
AxisFont = Service.PluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(
|
||||
new(GameFontFamilyAndSize.Axis14)
|
||||
);
|
||||
|
||||
Service.Plugin.Hooks.OnActionUsed += OnUseAction;
|
||||
|
||||
@@ -83,7 +85,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
MinimumSize = new(494, -1),
|
||||
MaximumSize = new(494, 10000)
|
||||
MaximumSize = new(494, 10000),
|
||||
};
|
||||
|
||||
TitleBarButtons =
|
||||
@@ -93,14 +95,15 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
Icon = FontAwesomeIcon.Cog,
|
||||
IconOffset = new(2, 1),
|
||||
Click = _ => Service.Plugin.OpenSettingsTab("Synthesis Helper"),
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings")
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings"),
|
||||
},
|
||||
new() {
|
||||
new()
|
||||
{
|
||||
Icon = FontAwesomeIcon.Heart,
|
||||
IconOffset = new(2, 1),
|
||||
Click = _ => Util.OpenLink(Plugin.Plugin.SupportLink),
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!")
|
||||
}
|
||||
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!"),
|
||||
},
|
||||
];
|
||||
|
||||
Service.WindowSystem.AddWindow(this);
|
||||
@@ -153,10 +156,10 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
WasCalculatable = ShouldCalculate;
|
||||
}
|
||||
|
||||
public override bool DrawConditions() =>
|
||||
ShouldOpen;
|
||||
public override bool DrawConditions() => ShouldOpen;
|
||||
|
||||
private bool wasInCraftAction;
|
||||
|
||||
private bool CalculateShouldOpen()
|
||||
{
|
||||
if (Service.Objects.LocalPlayer == null)
|
||||
@@ -209,7 +212,8 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
OnStartCrafting(recipeId);
|
||||
OnStateUpdated();
|
||||
|
||||
if (Service.Configuration.CollapseSynthHelper) ShouldCollapse = true;
|
||||
if (Service.Configuration.CollapseSynthHelper)
|
||||
ShouldCollapse = true;
|
||||
}
|
||||
|
||||
if (IsRecalculateQueued)
|
||||
@@ -228,6 +232,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
private Vector2? LastPosition { get; set; }
|
||||
private byte? StyleAlpha { get; set; }
|
||||
private byte? LastAlpha { get; set; }
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
base.PreDraw();
|
||||
@@ -239,7 +244,9 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
ref var unit = ref Addon->AtkUnitBase;
|
||||
var scale = unit.Scale;
|
||||
var pos = new Vector2(unit.X, unit.Y);
|
||||
var size = new Vector2(unit.WindowNode->AtkResNode.Width, unit.WindowNode->AtkResNode.Height) * scale;
|
||||
var size =
|
||||
new Vector2(unit.WindowNode->AtkResNode.Width, unit.WindowNode->AtkResNode.Height)
|
||||
* scale;
|
||||
|
||||
var offset = 5;
|
||||
|
||||
@@ -261,7 +268,10 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
WindowName = WindowNameFloating;
|
||||
}
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, StyleAlpha.HasValue ? (StyleAlpha.Value / 255f) : 1);
|
||||
ImGui.PushStyleVar(
|
||||
ImGuiStyleVar.Alpha,
|
||||
StyleAlpha.HasValue ? (StyleAlpha.Value / 255f) : 1
|
||||
);
|
||||
}
|
||||
|
||||
public override void PostDraw()
|
||||
@@ -273,7 +283,6 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
|
||||
if (ShouldCollapse)
|
||||
{
|
||||
ImGui.SetWindowCollapsed(true);
|
||||
@@ -298,7 +307,9 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
}
|
||||
|
||||
private SimulationState? hoveredState;
|
||||
private SimulationState DisplayedState => hoveredState ?? (Service.Configuration.SynthHelperDisplayOnlyFirstStep ? Macro.FirstState : Macro.State);
|
||||
private SimulationState DisplayedState =>
|
||||
hoveredState
|
||||
?? (Service.Configuration.SynthHelperDisplayOnlyFirstStep ? Macro.FirstState : Macro.State);
|
||||
|
||||
private void DrawMacro()
|
||||
{
|
||||
@@ -308,7 +319,11 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
var lastState = Macro.InitialState;
|
||||
hoveredState = null;
|
||||
|
||||
var itemsPerRow = (int)Math.Max(1, MathF.Floor((ImGui.GetContentRegionAvail().X + spacing) / (imageSize + spacing)));
|
||||
var itemsPerRow = (int)
|
||||
Math.Max(
|
||||
1,
|
||||
MathF.Floor((ImGui.GetContentRegionAvail().X + spacing) / (imageSize + spacing))
|
||||
);
|
||||
|
||||
using var _color = ImRaii.PushColor(ImGuiCol.Button, Vector4.Zero);
|
||||
using var _color3 = ImRaii.PushColor(ImGuiCol.ButtonHovered, Vector4.Zero);
|
||||
@@ -328,9 +343,18 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
var offsetVec2 = ImGui.GetStyle().ItemSpacing / 2;
|
||||
var offset = new Vector2((offsetVec2.X + offsetVec2.Y) / 2f);
|
||||
var color = canExecute ? ImGuiColors.DalamudWhite2 : ImGuiColors.DalamudGrey3;
|
||||
ImGui.GetWindowDrawList().AddRectFilled(pos - offset, pos + new Vector2(imageSize) + offset, ImGui.GetColorU32(color), 4);
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRectFilled(
|
||||
pos - offset,
|
||||
pos + new Vector2(imageSize) + offset,
|
||||
ImGui.GetColorU32(color),
|
||||
4
|
||||
);
|
||||
}
|
||||
bool isHovered, isHeld, isPressed;
|
||||
bool isHovered,
|
||||
isHeld,
|
||||
isPressed;
|
||||
{
|
||||
var pos = ImGui.GetCursorScreenPos();
|
||||
var offset = ImGui.GetStyle().ItemSpacing / 2f;
|
||||
@@ -344,9 +368,23 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
var id = ImGui.GetID($"###ButtonContainer");
|
||||
var isClipped = !ImGuiExtras.ItemAdd(bb, id, out _, 0);
|
||||
|
||||
isPressed = ImGuiExtras.ButtonBehavior(bb, id, out isHovered, out isHeld, ImGuiButtonFlags.None);
|
||||
isPressed = ImGuiExtras.ButtonBehavior(
|
||||
bb,
|
||||
id,
|
||||
out isHovered,
|
||||
out isHeld,
|
||||
ImGuiButtonFlags.None
|
||||
);
|
||||
}
|
||||
ImGui.ImageButton(action.GetIcon(RecipeData!.ClassJob).Handle, new(imageSize), default, Vector2.One, 0, default, failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One);
|
||||
ImGui.ImageButton(
|
||||
action.GetIcon(RecipeData!.ClassJob).Handle,
|
||||
new(imageSize),
|
||||
default,
|
||||
Vector2.One,
|
||||
0,
|
||||
default,
|
||||
failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One
|
||||
);
|
||||
if (isPressed && i == 0)
|
||||
{
|
||||
if (ExecuteNextAction())
|
||||
@@ -354,15 +392,21 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
}
|
||||
if (isHovered)
|
||||
{
|
||||
ImGuiUtils.Tooltip($"{action.GetName(RecipeData!.ClassJob)}\n" +
|
||||
$"{actionBase.GetTooltip(CreateSim(lastState), true)}" +
|
||||
$"{(canExecute && i == 0 ? "Click or run /craftaction to execute" : string.Empty)}");
|
||||
ImGuiUtils.Tooltip(
|
||||
$"{action.GetName(RecipeData!.ClassJob)}\n"
|
||||
+ $"{actionBase.GetTooltip(CreateSim(lastState), true)}"
|
||||
+ $"{(canExecute && i == 0 ? "Click or run /craftaction to execute" : string.Empty)}"
|
||||
);
|
||||
hoveredState = state;
|
||||
}
|
||||
lastState = state;
|
||||
}
|
||||
|
||||
var rows = (int)Math.Max(1, MathF.Ceiling(Service.Configuration.SynthHelperMaxDisplayCount / itemsPerRow));
|
||||
var rows = (int)
|
||||
Math.Max(
|
||||
1,
|
||||
MathF.Ceiling(Service.Configuration.SynthHelperMaxDisplayCount / itemsPerRow)
|
||||
);
|
||||
for (var i = 0; i < rows; ++i)
|
||||
{
|
||||
if (count <= i * itemsPerRow)
|
||||
@@ -381,7 +425,15 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
var iconHeight = ImGui.GetFrameHeight() * 1.75f;
|
||||
var durationShift = iconHeight * .2f;
|
||||
|
||||
ImGui.Dummy(new(0, iconHeight + ImGui.GetStyle().ItemSpacing.Y + ImGui.GetTextLineHeight() - durationShift));
|
||||
ImGui.Dummy(
|
||||
new(
|
||||
0,
|
||||
iconHeight
|
||||
+ ImGui.GetStyle().ItemSpacing.Y
|
||||
+ ImGui.GetTextLineHeight()
|
||||
- durationShift
|
||||
)
|
||||
);
|
||||
ImGui.SameLine(0, 0);
|
||||
|
||||
var effects = state.ActiveEffects;
|
||||
@@ -413,35 +465,92 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
var reliability = Macro.GetReliability(RecipeData!, Service.Configuration.SynthHelperDisplayOnlyFirstStep ? 0 : ^1);
|
||||
var reliability = Macro.GetReliability(
|
||||
RecipeData!,
|
||||
Service.Configuration.SynthHelperDisplayOnlyFirstStep ? 0 : ^1
|
||||
);
|
||||
{
|
||||
var mainBars = new List<DynamicBars.BarData>()
|
||||
{
|
||||
new("Progress", Colors.Progress, reliability.Progress, state.Progress, RecipeData!.RecipeInfo.MaxProgress),
|
||||
new("Quality", Colors.Quality, reliability.Quality, state.Quality, RecipeData.RecipeInfo.MaxQuality),
|
||||
new(
|
||||
"Progress",
|
||||
Colors.Progress,
|
||||
reliability.Progress,
|
||||
state.Progress,
|
||||
RecipeData!.RecipeInfo.MaxProgress
|
||||
),
|
||||
new(
|
||||
"Quality",
|
||||
Colors.Quality,
|
||||
reliability.Quality,
|
||||
state.Quality,
|
||||
RecipeData.RecipeInfo.MaxQuality
|
||||
),
|
||||
new("CP", Colors.CP, state.CP, CharacterStats!.CP),
|
||||
};
|
||||
if (RecipeData.RecipeInfo.MaxQuality <= 0)
|
||||
mainBars.RemoveAt(1);
|
||||
var halfBars = new List<DynamicBars.BarData>()
|
||||
{
|
||||
new("Durability", Colors.Durability, state.Durability, RecipeData.RecipeInfo.MaxDurability),
|
||||
new(
|
||||
"Durability",
|
||||
Colors.Durability,
|
||||
state.Durability,
|
||||
RecipeData.RecipeInfo.MaxDurability
|
||||
),
|
||||
};
|
||||
if (RecipeData.IsCollectable)
|
||||
halfBars.Add(new("Collectability", Colors.Collectability, reliability.ParamScore, state.Collectability, state.MaxCollectability, RecipeData.CollectableThresholds, $"{state.Collectability}", $"{state.MaxCollectability:0}"));
|
||||
halfBars.Add(
|
||||
new(
|
||||
"Collectability",
|
||||
Colors.Collectability,
|
||||
reliability.ParamScore,
|
||||
state.Collectability,
|
||||
state.MaxCollectability,
|
||||
RecipeData.CollectableThresholds,
|
||||
$"{state.Collectability}",
|
||||
$"{state.MaxCollectability:0}"
|
||||
)
|
||||
);
|
||||
else if (RecipeData.Recipe.RequiredQuality > 0)
|
||||
{
|
||||
var qualityPercent = (float)state.Quality / RecipeData.Recipe.RequiredQuality * 100;
|
||||
halfBars.Add(new("Quality %", Colors.HQ, reliability.ParamScore, qualityPercent, 100, null, $"{qualityPercent:0}%", null));
|
||||
halfBars.Add(
|
||||
new(
|
||||
"Quality %",
|
||||
Colors.HQ,
|
||||
reliability.ParamScore,
|
||||
qualityPercent,
|
||||
100,
|
||||
null,
|
||||
$"{qualityPercent:0}%",
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (RecipeData.RecipeInfo.MaxQuality > 0)
|
||||
halfBars.Add(new("HQ %", Colors.HQ, reliability.ParamScore, state.HQPercent, 100, null, $"{state.HQPercent}%", null));
|
||||
halfBars.Add(
|
||||
new(
|
||||
"HQ %",
|
||||
Colors.HQ,
|
||||
reliability.ParamScore,
|
||||
state.HQPercent,
|
||||
100,
|
||||
null,
|
||||
$"{state.HQPercent}%",
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
if (halfBars.Count > 1)
|
||||
{
|
||||
var textSize = DynamicBars.GetTextSize(mainBars.Concat(halfBars));
|
||||
DynamicBars.Draw(mainBars, textSize);
|
||||
using var table = ImRaii.Table($"##{nameof(SynthHelper)}_halfbars", halfBars.Count, ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.SizingStretchSame);
|
||||
using var table = ImRaii.Table(
|
||||
$"##{nameof(SynthHelper)}_halfbars",
|
||||
halfBars.Count,
|
||||
ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.SizingStretchSame
|
||||
);
|
||||
if (table)
|
||||
{
|
||||
foreach (var bar in halfBars)
|
||||
@@ -467,7 +576,9 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
using var _disabled = ImRaii.Disabled();
|
||||
ImGui.Button("Stopping", new(-1, 0));
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.TooltipWrapped("This might could a while, sorry! Please report if this takes longer than a second.");
|
||||
ImGuiUtils.TooltipWrapped(
|
||||
"This might could a while, sorry! Please report if this takes longer than a second."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -480,13 +591,22 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
if (ImGui.Button("Retry", new(-1, 0)))
|
||||
AttemptRetry();
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtils.TooltipWrapped("Suggest a way to finish the crafting recipe. " +
|
||||
"Results aren't perfect, and levels of success " +
|
||||
"can vary wildly depending on the solver's settings.");
|
||||
ImGuiUtils.TooltipWrapped(
|
||||
"Suggest a way to finish the crafting recipe. "
|
||||
+ "Results aren't perfect, and levels of success "
|
||||
+ "can vary wildly depending on the solver's settings."
|
||||
);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Open in Macro Editor", new(-1, 0)))
|
||||
Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.Objects.LocalPlayer!.StatusList), null, [], null);
|
||||
Service.Plugin.OpenMacroEditor(
|
||||
CharacterStats!,
|
||||
RecipeData!,
|
||||
new(Service.Objects.LocalPlayer!.StatusList),
|
||||
null,
|
||||
[],
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public bool ExecuteNextAction()
|
||||
@@ -519,13 +639,20 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
{
|
||||
var gearStats = Gearsets.CalculateGearsetCurrentStats();
|
||||
|
||||
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
||||
var container = InventoryManager
|
||||
.Instance()
|
||||
->GetInventoryContainer(InventoryType.EquippedItems);
|
||||
if (container == null)
|
||||
throw new InvalidOperationException("Could not get inventory container");
|
||||
|
||||
var gearItems = Gearsets.GetGearsetItems(container);
|
||||
|
||||
var characterStats = Gearsets.CalculateCharacterStats(gearStats, gearItems, RecipeData.ClassJob.GetPlayerLevel(), RecipeData.ClassJob.CanPlayerUseManipulation());
|
||||
var characterStats = Gearsets.CalculateCharacterStats(
|
||||
gearStats,
|
||||
gearItems,
|
||||
RecipeData.ClassJob.GetPlayerLevel(),
|
||||
RecipeData.ClassJob.CanPlayerUseManipulation()
|
||||
);
|
||||
if (characterStats != CharacterStats)
|
||||
{
|
||||
CharacterStats = characterStats;
|
||||
@@ -554,8 +681,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
CurrentActionStates = CurrentState.ActionStates;
|
||||
}
|
||||
|
||||
private void RefreshCurrentState() =>
|
||||
CurrentState = GetCurrentState();
|
||||
private void RefreshCurrentState() => CurrentState = GetCurrentState();
|
||||
|
||||
private SimulationState GetCurrentState()
|
||||
{
|
||||
@@ -602,7 +728,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
TrainedPerfection = HasEffect((ushort)EffectType.TrainedPerfection.StatusId()),
|
||||
HeartAndSoul = HasEffect((ushort)EffectType.HeartAndSoul.StatusId()),
|
||||
},
|
||||
ActionStates = CurrentActionStates
|
||||
ActionStates = CurrentActionStates,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -638,7 +764,11 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
SolverTask.Start();
|
||||
}
|
||||
|
||||
private int CalculateBestMacroTask(SimulationState state, CancellationToken token, bool hasDelineations)
|
||||
private int CalculateBestMacroTask(
|
||||
SimulationState state,
|
||||
CancellationToken token,
|
||||
bool hasDelineations
|
||||
)
|
||||
{
|
||||
var config = Service.Configuration.SynthHelperSolverConfig;
|
||||
var canUseDelineations = !Service.Configuration.CheckDelineations || hasDelineations;
|
||||
@@ -663,12 +793,17 @@ public sealed unsafe class SynthHelper : Window, IDisposable
|
||||
private void EnqueueAction(ActionType action)
|
||||
{
|
||||
var newSize = Macro.Enqueue(action, Service.Configuration.SynthHelperMaxDisplayCount);
|
||||
if (newSize >= Service.Configuration.SynthHelperStepCount || newSize >= Service.Configuration.SynthHelperMaxDisplayCount)
|
||||
if (
|
||||
newSize >= Service.Configuration.SynthHelperStepCount
|
||||
|| newSize >= Service.Configuration.SynthHelperMaxDisplayCount
|
||||
)
|
||||
SolverTask?.Cancel();
|
||||
}
|
||||
|
||||
private static Sim CreateSim(in SimulationState state) =>
|
||||
Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state };
|
||||
Service.Configuration.ConditionRandomness
|
||||
? new Sim() { State = state }
|
||||
: new SimNoRandom() { State = state };
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Forgeimizer
|
||||
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/actions/workflows/build.yml)
|
||||
[](LICENSE)
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/releases/latest)
|
||||
[](https://github.com/goatcorp/Dalamud)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://www.finalfantasyxiv.com/)
|
||||
@@ -10,12 +12,17 @@
|
||||
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
|
||||
</p>
|
||||
|
||||
**Version 2.9.1.1** — a Hellion Forge maintenance fork of
|
||||
[Craftimizer](https://github.com/WorkingRobot/Craftimizer) by
|
||||
**Version 0.1.0** — a Hellion Forge maintenance fork of
|
||||
[Craftimizer](https://github.com/WorkingRobot/Craftimizer) 2.9.1.1 by
|
||||
[Asriel Camora](https://github.com/WorkingRobot), brought back to life on
|
||||
Dalamud SDK 15 for FFXIV 7.5+. Installs as a standalone plugin under the name
|
||||
**Forgeimizer**, refuses to load while upstream Craftimizer is active.
|
||||
|
||||
Forgeimizer uses an independent version line (starting at 0.1.0). The
|
||||
upstream Craftimizer version the current fork is built on is always
|
||||
called out in the description above and the changelog entry for each
|
||||
release. See [`CHANGELOG.md`](CHANGELOG.md) for the full release history.
|
||||
|
||||
Upstream Craftimizer received its last update for FFXIV 7.4 in late 2025 and
|
||||
has been dormant since. With the Dalamud API jump from level 14 to 15 in
|
||||
early 2026, the plugin stopped loading. Forgeimizer is a narrow
|
||||
@@ -117,11 +124,11 @@ All upstream Craftimizer slash commands work unchanged, plus one alias:
|
||||
|
||||
| Command | What it does |
|
||||
| -------------------------------------- | ------------------------------------------------ |
|
||||
| `/craftimizer` *or* **`/forgeimizer`** | Open the settings window |
|
||||
| `/crafteditor` *or* `/macroeditor` | Open the crafting macro editor |
|
||||
| `/craftimizer` _or_ **`/forgeimizer`** | Open the settings window |
|
||||
| `/crafteditor` _or_ `/macroeditor` | Open the crafting macro editor |
|
||||
| `/craftaction` | Execute the next suggested action in synth helper |
|
||||
| `/craftretry` | Click "Retry" in the synthesis helper |
|
||||
| `/craftmacros` *or* `/macrolist` | Open the crafting macros window |
|
||||
| `/craftmacros` _or_ `/macrolist` | Open the crafting macros window |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/dotnet-tools.json",
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "1.2.6",
|
||||
"commands": ["csharpier"],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":dependencyDashboard",
|
||||
":semanticCommits",
|
||||
":timezone(Europe/Berlin)",
|
||||
"schedule:weekly"
|
||||
],
|
||||
"labels": ["dependencies", "renovate"],
|
||||
"assignees": ["JonKazama-Hellion"],
|
||||
"prHourlyLimit": 10,
|
||||
"prConcurrentLimit": 20,
|
||||
"rebaseWhen": "behind-base-branch",
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Group all minor and patch updates per ecosystem in one PR",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"groupName": "minor and patch updates ({{manager}})"
|
||||
},
|
||||
{
|
||||
"description": "Major updates always get their own PR with breaking-change label",
|
||||
"matchUpdateTypes": ["major"],
|
||||
"labels": ["dependencies", "major-update", "breaking-change"],
|
||||
"addLabels": ["needs-review"]
|
||||
},
|
||||
{
|
||||
"description": "Dev dependencies in their own group",
|
||||
"matchDepTypes": ["devDependencies"],
|
||||
"groupName": "dev dependencies"
|
||||
},
|
||||
{
|
||||
"description": "Pin GitHub Action versions by SHA for supply-chain hygiene",
|
||||
"matchManagers": ["github-actions"],
|
||||
"pinDigests": true,
|
||||
"ignorePaths": [".gitea/workflows/**"]
|
||||
}
|
||||
],
|
||||
"vulnerabilityAlerts": {
|
||||
"labels": ["security", "vulnerability"],
|
||||
"schedule": ["at any time"]
|
||||
},
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": ["before 6am on monday"],
|
||||
"commitMessageAction": "Refresh"
|
||||
},
|
||||
"osvVulnerabilityAlerts": true
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
[
|
||||
{
|
||||
"Author": "Asriel Camora (original); Jon Kazama / Hellion Forge (fork)",
|
||||
"Name": "Forgeimizer",
|
||||
"InternalName": "Forgeimizer",
|
||||
"AssemblyVersion": "0.1.0.0",
|
||||
"Description": "A Hellion Forge plugin — maintenance fork of Craftimizer by Asriel Camora, brought back to life on Dalamud SDK 15 for FFXIV 7.5+.\n\nSimulate crafts, create computer-assisted macros, and get mid-craft suggestions from the comfort of your own game. Open your crafting log to get started.\n\nAll features come from upstream Craftimizer, unchanged in this fork:\n- Crafting simulator with full action state tracking\n- Macro solver (Raphael Rust crate, bundled)\n- Recipe note overlay\n- Synthesis helper window\n- Macro editor and macro list\n\nThis fork:\n- Refuses to load if upstream Craftimizer is active (no parallel hooks)\n- No features added, no design changes vs upstream\n- Will archive if Asriel ships an official SDK 15 update upstream\n\nBased on Craftimizer 2.9.1.1 (upstream WorkingRobot/Craftimizer, MIT).\nSource: https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer",
|
||||
"ApplicableVersion": "any",
|
||||
"RepoUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer",
|
||||
"Tags": [
|
||||
"crafting",
|
||||
"doh",
|
||||
"craft",
|
||||
"macro",
|
||||
"solver",
|
||||
"generator",
|
||||
"generate",
|
||||
"simulate",
|
||||
"sim",
|
||||
"simulator",
|
||||
"hellion",
|
||||
"forge"
|
||||
],
|
||||
"DalamudApiLevel": 15,
|
||||
"LoadRequiredState": 0,
|
||||
"LoadSync": false,
|
||||
"CanUnloadAsync": false,
|
||||
"LoadPriority": 0,
|
||||
"Punchline": "A Hellion Forge plugin. Crafting simulator and macro solver for FFXIV, maintenance fork of Asriel Camora's Craftimizer kept current for Dalamud API 15+.",
|
||||
"Changelog": "**v0.1.0 — First Hellion fork release (2026-05-26)**\n\nInitial Hellion Forge maintenance release of the Craftimizer fork. Combines the Dalamud SDK 15 migration, the rebrand to Forgeimizer, and the conflict detector into one shippable plugin.\n\n- Dalamud SDK 14 → 15 for FFXIV 7.5+ compatibility. Migrates the call sites the SDK 15 compiler refused (ValueType → AtkValueType, local IEndObject interface, typed ImRaii disposables, ImRaii.TabItem ref-bool lifetime fix).\n- Rebrand to Forgeimizer. Assembly name, plugin manifest, WindowSystem name, About-tab header, MacroMate default, and a new /forgeimizer slash command alias all use the new name. The plugin installs into its own pluginConfigs/Forgeimizer/ slot.\n- Conflict detector. Forgeimizer refuses to load if upstream Craftimizer is active in the same Dalamud instance.\n- Hellion Forge custom repo distribution.\n\nInternal namespaces (Craftimizer.*) intentionally left alone. Crafting logic, solver, simulator, recipe data layer, synthesis hooks, macro engine, and all UI windows are unchanged from upstream Craftimizer 2.9.1.1.\n\nBased on Craftimizer 2.9.1.1 (upstream WorkingRobot/Craftimizer, MIT).",
|
||||
"AcceptsFeedback": true,
|
||||
"DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/releases/download/v0.1.0/latest.zip",
|
||||
"DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/releases/download/v0.1.0/latest.zip",
|
||||
"DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/releases/download/v0.1.0/latest.zip",
|
||||
"TestingAssemblyVersion": "0.1.0.0",
|
||||
"IconUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/raw/branch/main/icon.png",
|
||||
"ImageUrls": [
|
||||
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/raw/branch/main/Images/RecipeNote.png",
|
||||
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/raw/branch/main/Images/SynthHelper.png",
|
||||
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer/raw/branch/main/Images/MacroEditor.png"
|
||||
],
|
||||
"DownloadCount": 0,
|
||||
"IsHide": false,
|
||||
"IsTestingExclusive": false,
|
||||
"CategoryTags": [
|
||||
"jobs"
|
||||
]
|
||||
}
|
||||
]
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# preflight.sh — pre-push gate. Block A verifies that csproj and repo.json
|
||||
# agree on the version and tag URLs. Block B does a headless `dotnet build`
|
||||
# to catch compile-time API drift. Block C runs `dotnet csharpier check`
|
||||
# against Craftimizer/. Block D runs markdownlint against repo *.md files.
|
||||
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
echo "==> preflight: Block A — version consistency"
|
||||
./scripts/verify-version-consistency.sh
|
||||
|
||||
echo "==> preflight: Block B — plugin compile health"
|
||||
dotnet build Craftimizer/Craftimizer.csproj --configuration Release --nologo --verbosity quiet
|
||||
|
||||
echo "==> preflight: Block C — csharpier reflow check"
|
||||
dotnet csharpier check Craftimizer/
|
||||
|
||||
echo "==> preflight: Block D — markdownlint"
|
||||
# npx --yes avoids a global install; first run caches into ~/.npm/_npx/.
|
||||
# Subsequent runs are sub-second.
|
||||
npx --yes markdownlint-cli2 "**/*.md" "#node_modules" "#bin" "#obj" "#.claude" "#CLAUDE.md"
|
||||
|
||||
echo "==> preflight: ALL GREEN"
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# setup-hooks.sh — installs pre-push hook via core.hooksPath. Idempotent.
|
||||
# Note: NO pre-commit hook — Forgeimizer is a small maintenance fork with
|
||||
# no separate test harness; version/manifest/build/lint checks happen on
|
||||
# pre-push only.
|
||||
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
git config core.hooksPath .githooks
|
||||
chmod +x .githooks/pre-push
|
||||
chmod +x scripts/preflight.sh
|
||||
chmod +x scripts/verify-version-consistency.sh
|
||||
echo "setup-hooks: core.hooksPath set to .githooks. pre-push live."
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# verify-version-consistency.sh — Block A of preflight.
|
||||
# csproj <Version> is 3-digit SemVer; repo.json AssemblyVersion is 4-digit (.0 suffix).
|
||||
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
CSPROJ="$ROOT/Craftimizer/Craftimizer.csproj"
|
||||
REPO_JSON="$ROOT/repo.json"
|
||||
|
||||
fail() { echo "verify-version-consistency: FAIL — $1" >&2; exit 1; }
|
||||
ok() { echo "verify-version-consistency: OK — $1"; }
|
||||
|
||||
CSPROJ_VER="$(grep -oE '<Version>[^<]+</Version>' "$CSPROJ" | head -1 | sed -E 's/<[^>]+>//g')"
|
||||
[ -n "$CSPROJ_VER" ] || fail "$CSPROJ has no <Version> element"
|
||||
|
||||
EXPECTED_4DIGIT="${CSPROJ_VER}.0"
|
||||
|
||||
REPO_VER="$(jq -r '.[0].AssemblyVersion' "$REPO_JSON")"
|
||||
[ "$REPO_VER" = "$EXPECTED_4DIGIT" ] \
|
||||
|| fail "csproj=$CSPROJ_VER expects repo.json AssemblyVersion=$EXPECTED_4DIGIT but got $REPO_VER. Fix: align in $REPO_JSON."
|
||||
|
||||
TEST_VER="$(jq -r '.[0].TestingAssemblyVersion' "$REPO_JSON")"
|
||||
[ "$TEST_VER" = "$EXPECTED_4DIGIT" ] \
|
||||
|| fail "TestingAssemblyVersion=$TEST_VER must match $EXPECTED_4DIGIT. Fix: align in $REPO_JSON."
|
||||
|
||||
TAG="v$CSPROJ_VER"
|
||||
for KEY in DownloadLinkInstall DownloadLinkUpdate DownloadLinkTesting; do
|
||||
URL="$(jq -r ".[0].$KEY" "$REPO_JSON")"
|
||||
case "$URL" in
|
||||
*"/$TAG/"*) ;;
|
||||
*) fail "$KEY=$URL does not contain tag $TAG. Fix: update $REPO_JSON $KEY to releases/download/$TAG/latest.zip." ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ok "csproj=$CSPROJ_VER, repo.json=$EXPECTED_4DIGIT, tag $TAG present in DownloadLinks"
|
||||
Reference in New Issue
Block a user