Initial import of BardFrontline
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
using System.Numerics;
|
||||
using BardFrontline.Core;
|
||||
using BardFrontline.Runtime;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace BardFrontline;
|
||||
|
||||
public sealed class Plugin : IDalamudPlugin
|
||||
{
|
||||
private readonly IDalamudPluginInterface pi;
|
||||
private readonly ICommandManager commands;
|
||||
private readonly IFramework framework;
|
||||
private readonly Configuration configuration;
|
||||
private readonly CombatRuntime runtime;
|
||||
private bool settingsOpen;
|
||||
private string? frozenDiagnostic;
|
||||
|
||||
public Plugin(IDalamudPluginInterface pluginInterface, ICommandManager commandManager,
|
||||
IFramework framework, IObjectTable objects, IClientState client, ICondition condition,
|
||||
IDutyState duty, IDataManager data, ITargetManager targets, IPluginLog log)
|
||||
{
|
||||
pi = pluginInterface; commands = commandManager; this.framework = framework;
|
||||
configuration = pi.GetPluginConfig() as Configuration ?? new();
|
||||
configuration.Settings ??= new(); configuration.Settings.Normalize();
|
||||
runtime = new(objects, client, condition, duty, data, targets, log, configuration.Settings);
|
||||
commands.AddHandler("/bardpvp", new CommandInfo(OnCommand)
|
||||
{ HelpMessage = "设置;on/off/toggle 开关;manual/auto 手动接管/恢复;burst 开启10秒爆发,burst off 结束;backstep 切换后跳手动/自动。" });
|
||||
pi.UiBuilder.Draw += Draw;
|
||||
pi.UiBuilder.OpenConfigUi += OpenSettings;
|
||||
pi.UiBuilder.OpenMainUi += OpenSettings;
|
||||
framework.Update += runtime.Update;
|
||||
}
|
||||
|
||||
private void OpenSettings() => settingsOpen = true;
|
||||
private void OnCommand(string command, string args)
|
||||
{
|
||||
lock (runtime.SyncRoot) HandleCommand(args);
|
||||
}
|
||||
private void HandleCommand(string args)
|
||||
{
|
||||
switch (args.Trim().ToLowerInvariant())
|
||||
{
|
||||
case "on": runtime.Enabled = true; break;
|
||||
case "off": runtime.Enabled = false; break;
|
||||
case "toggle": runtime.Enabled = !runtime.Enabled; break;
|
||||
case "manual": runtime.ManualPaused = true; break;
|
||||
case "auto": runtime.ManualPaused = false; break;
|
||||
case "burst": runtime.StartBurst(); break;
|
||||
case "burst off": runtime.StopBurst(); break;
|
||||
case "backstep": configuration.Settings.ManualRepelling = !configuration.Settings.ManualRepelling; Save(); break;
|
||||
case "backstep manual": configuration.Settings.ManualRepelling = true; Save(); break;
|
||||
case "backstep auto": configuration.Settings.ManualRepelling = false; Save(); break;
|
||||
default: settingsOpen = true; break;
|
||||
}
|
||||
}
|
||||
private void Save() { configuration.Settings.Normalize(); pi.SavePluginConfig(configuration); }
|
||||
private void RunToggle()
|
||||
{
|
||||
var running = runtime.Enabled;
|
||||
if (ImGui.Checkbox("自动运行##master", ref running)) runtime.Enabled = running;
|
||||
ImGui.TextWrapped(runtime.State);
|
||||
ImGui.TextWrapped($"模式:{runtime.ModeText}");
|
||||
ImGui.TextWrapped(runtime.ControlText);
|
||||
if (ImGui.Button(runtime.ManualPaused ? "恢复自动控制" : "手动接管")) runtime.ManualPaused = !runtime.ManualPaused;
|
||||
ImGui.SameLine(); if (ImGui.Button("爆发 10 秒")) runtime.StartBurst();
|
||||
}
|
||||
private void Draw()
|
||||
{
|
||||
lock (runtime.SyncRoot) DrawLocked();
|
||||
}
|
||||
private void DrawLocked()
|
||||
{
|
||||
var c = configuration.Settings;
|
||||
if (c.ShowOverlay)
|
||||
{
|
||||
ImGui.SetNextWindowSize(new Vector2(330, 0), ImGuiCond.FirstUseEver);
|
||||
if (ImGui.Begin("诗人前线##BardFrontlineHUD", ImGuiWindowFlags.AlwaysAutoResize))
|
||||
{
|
||||
RunToggle();
|
||||
ImGui.TextWrapped($"目标:{runtime.TargetText}");
|
||||
ImGui.TextWrapped($"下一技能:{runtime.NextText}");
|
||||
if (ImGui.Button("设置")) settingsOpen = true;
|
||||
}
|
||||
ImGui.End();
|
||||
}
|
||||
if (!settingsOpen) return;
|
||||
ImGui.SetNextWindowSize(new Vector2(570, 680), ImGuiCond.FirstUseEver);
|
||||
if (ImGui.Begin("Bard Frontline 设置", ref settingsOpen))
|
||||
{
|
||||
RunToggle();
|
||||
ImGui.TextWrapped("支持纷争前线和烈羽争锋;驾驶机甲时暂停诗人循环。每次加载总开关默认关闭。使用 /bardpvp off 可随时停止。后跳会改变角色位置。关闭后不再提交新动作,游戏已经接受的动作仍由游戏完成。");
|
||||
ImGui.Separator();
|
||||
Check("自动攻击循环与进攻增益", c.AutoAttack, v => c.AutoAttack = v);
|
||||
Check("无有效玩家时攻击战场目标", c.AttackObjectives, v => c.AttackObjectives = v);
|
||||
ImGui.TextWrapped("普通玩家优先;无可用玩家攻击时,攻击冰块、敌方兵线、建筑和机甲等合法目标。鹰眼仅对未驾驶机甲的玩家使用。");
|
||||
Check("无视敌方 Guard", c.IgnoreEnemyGuard, v => c.IgnoreEnemyGuard = v);
|
||||
ImGui.TextWrapped("允许向 Guard 目标释放攻击;不改变实际伤害减免。自身 Guard 期间始终暂停自动技能。");
|
||||
Check("礼赞帮助小队成员解除控制", c.HelpParty, v => c.HelpParty = v);
|
||||
Check("显示状态浮窗", c.ShowOverlay, v => c.ShowOverlay = v);
|
||||
var order = (int)c.TargetOrder;
|
||||
if (ImGui.Combo("选敌排序", ref order, "当前 HP 最低\0HP 比例最低\0距离最近\0攻击标记 1–8 优先\0"))
|
||||
{ c.TargetOrder = (TargetOrder)order; Save(); }
|
||||
var guard = c.GuardHpPercent;
|
||||
if (ImGui.SliderFloat("防御 HP %", ref guard, 1, 100, "%.0f%%")) { c.GuardHpPercent = guard; Save(); }
|
||||
var heal = c.RecuperateMissingHp;
|
||||
if (ImGui.InputInt("自疗所需缺失 HP", ref heal, 1000, 5000)) { c.RecuperateMissingHp = heal; Save(); }
|
||||
var elixir = c.ElixirPercent;
|
||||
if (ImGui.SliderFloat("补给 HP / MP %", ref elixir, 1, 100, "%.1f%%")) { c.ElixirPercent = elixir; Save(); }
|
||||
ImGui.TextWrapped("补给还要求静止、30 米内无敌方玩家;烈羽争锋的敌方机甲、兵线和建筑也计入威胁。碎冰战冰块不计入。防御、自疗、补给在复活 5 秒后启用。");
|
||||
if (ImGui.CollapsingHeader("Eagle Eye Shot 收割", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
var strategy = (int)c.EagleStrategy;
|
||||
if (ImGui.Combo("鹰眼使用策略", ref strategy, "优先收割,否则按原循环\0严格保留,只用于收割\0"))
|
||||
{ c.EagleStrategy = (EagleStrategy)strategy; Save(); }
|
||||
var hp = c.EagleFinisherHp;
|
||||
if (ImGui.InputInt("收割有效 HP 上限", ref hp, 500, 2000)) { c.EagleFinisherHp = hp; Save(); }
|
||||
ImGui.TextWrapped("有效 HP = 当前 HP + 吸收护盾。范围内满足阈值的敌人按有效 HP 最低优先,鹰眼抢在普通输出与极限技前释放;生存技能仍优先。阈值是筛选条件,不是精确伤害预测;可根据实战伤害调整。需装备鹰眼职能技能。");
|
||||
}
|
||||
if (ImGui.CollapsingHeader("技能独立开关", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
foreach (var skill in Actions.All)
|
||||
{
|
||||
var active = c.Enabled(skill);
|
||||
if (ImGui.Checkbox($"{Actions.Name(skill)}##skill{(uint)skill}", ref active))
|
||||
{ if (active) c.DisabledSkills.Remove(skill); else c.DisabledSkills.Add(skill); Save(); }
|
||||
}
|
||||
if (ImGui.CollapsingHeader("选敌稳定与手动接管"))
|
||||
{
|
||||
Check("启用选敌稳定", c.StableTargets, v => c.StableTargets = v);
|
||||
Float("目标保持秒数", c.TargetHoldSeconds, 0, 3, v => c.TargetHoldSeconds = v);
|
||||
Integer("切换 HP 优势", c.TargetHpMargin, 0, 20000, v => c.TargetHpMargin = v);
|
||||
ImGui.TextWrapped("默认仍即时选择最低当前 HP。开启后短时间保持有效目标;明显更优的目标、目标失效或鹰眼收割可立即切换。");
|
||||
Check("检测手动切目标 / 其他来源动作后暂时接管", c.DetectManualControl, v => c.DetectManualControl = v);
|
||||
Float("自动接管持续秒数", c.ManualControlSeconds, .5f, 15, v => c.ManualControlSeconds = v);
|
||||
ImGui.TextWrapped("接管期间暂停所有自动动作和选敌,包括生存技能。按钮或 /bardpvp manual 持续接管,auto 恢复。自动检测无法区分手动动作与其他插件动作。");
|
||||
}
|
||||
if (ImGui.CollapsingHeader("穿插、爆发与范围攻击"))
|
||||
{
|
||||
Check("自适应穿插预算", c.AdaptiveWeaving, v => c.AdaptiveWeaving = v);
|
||||
ImGui.TextWrapped(runtime.TimingText);
|
||||
ImGui.TextWrapped("根据本插件瞬发动作的确认及动画锁样本估算;不足 3 个样本时使用原来的 0.65 秒。不会绕过游戏锁。");
|
||||
var burst = (int)c.Burst;
|
||||
if (ImGui.Combo("爆发策略", ref burst, "原循环,不保留\0和弦箭等待增益 / 充能\0手动爆发窗口\0主动爆发\0"))
|
||||
{ c.Burst = (BurstStrategy)burst; Save(); }
|
||||
if (c.Burst == BurstStrategy.Pressure)
|
||||
ImGui.TextWrapped("优先消耗已就绪的完美音调 / 爆破箭,提前使用鹰眼和沉默。瞬发输出及进攻增益不额外等待穿插空间,可能短暂推迟普攻;仍遵守鹰眼保留策略、游戏动作锁和技能条件。生存及后跳设置不变。");
|
||||
Integer("和弦目标充能", c.BurstCharges, 1, 4, v => c.BurstCharges = v);
|
||||
Float("等待增益最多秒数", c.BurstMaxHoldSeconds, .5f, 10, v => c.BurstMaxHoldSeconds = v);
|
||||
ImGui.TextWrapped("手动窗口仅保留和弦箭、职能增益及极限技;安可、鹰眼收割和生存不等待。按钮或 /bardpvp burst 开启 10 秒窗口。");
|
||||
if (ImGui.Button("结束爆发窗口")) runtime.StopBurst();
|
||||
Check("范围技能优先更多有效目标", c.PreferAoe, v => c.PreferAoe = v);
|
||||
Integer("范围选敌最少命中数", c.AoeMinTargets, 2, 10, v => c.AoeMinTargets = v);
|
||||
ImGui.TextWrapped("仍优先玩家。按客户端范围和目标位置估算圆形 / 直线覆盖;未达到数量时回到原排序。命中数是估算,不保证实际命中。");
|
||||
}
|
||||
if (ImGui.CollapsingHeader("战场目标分类"))
|
||||
{
|
||||
foreach (var kind in Enum.GetValues<ObjectiveKind>())
|
||||
{
|
||||
var attack = !c.DisabledObjectives.Contains(kind);
|
||||
if (ImGui.Checkbox($"攻击{Battlefield.KindName(kind)}", ref attack))
|
||||
{ if (attack) c.DisabledObjectives.Remove(kind); else c.DisabledObjectives.Add(kind); Save(); }
|
||||
}
|
||||
ImGui.TextWrapped("分类开关受战场目标总开关控制;全开保持原有偏好。未知类型归入其他目标。关闭攻击某类不会忽略它的补给威胁。");
|
||||
}
|
||||
if (ImGui.CollapsingHeader("集火生存与后跳"))
|
||||
{
|
||||
Check("启用集火 / 快速掉血防御", c.FocusDefense, v => c.FocusDefense = v);
|
||||
Integer("集火指向人数", c.FocusAttackers, 2, 10, v => c.FocusAttackers = v);
|
||||
Float("集火时防御 HP %", c.FocusGuardPercent, 1, 80, v => c.FocusGuardPercent = v);
|
||||
ImGui.TextWrapped("附近敌人目标或读条指向自己达到人数,或 2 秒累计掉血达到最大 HP 的 25% 时,按集火阈值防御,并将自疗缺失阈值降低到最多 6000。目标指向只是风险信号。");
|
||||
Check("后跳改为手动释放", c.ManualRepelling, v => c.ManualRepelling = v);
|
||||
ImGui.TextWrapped("默认保持自动后跳。可用 /bardpvp backstep 随时切换;手动模式由你按游戏技能,不影响其他循环。");
|
||||
}
|
||||
if (ImGui.CollapsingHeader("净化触发控制"))
|
||||
{
|
||||
string[] labels = ["眩晕", "加重", "止步", "沉默", "深度冻结", "自然的奇迹"];
|
||||
for (var i = 0; i < Statuses.Controls.Length; i++)
|
||||
{
|
||||
var id = Statuses.Controls[i]; var active = !c.DisabledPurifyStatuses.Contains(id);
|
||||
if (ImGui.Checkbox(labels[i], ref active))
|
||||
{ if (active) c.DisabledPurifyStatuses.Remove(id); else c.DisabledPurifyStatuses.Add(id); Save(); }
|
||||
}
|
||||
}
|
||||
if (ImGui.CollapsingHeader("运行诊断"))
|
||||
{
|
||||
if (ImGui.Button("复制诊断")) ImGui.SetClipboardText(frozenDiagnostic ?? runtime.DiagnosticText());
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(frozenDiagnostic == null ? "冻结快照" : "恢复实时"))
|
||||
frozenDiagnostic = frozenDiagnostic == null ? runtime.DiagnosticText() : null;
|
||||
ImGui.TextWrapped($"最近请求:{runtime.LastAction}");
|
||||
if (frozenDiagnostic != null) ImGui.TextWrapped(frozenDiagnostic);
|
||||
else
|
||||
{
|
||||
ImGui.TextWrapped(runtime.AuditSummary);
|
||||
ImGui.TextWrapped("每 0.25 秒重建完整快照。就绪检查与执行锁分开;复制内容含资源、状态、候选目标和近期事件。");
|
||||
foreach (var (skill, reason) in runtime.Diagnostics) ImGui.TextWrapped($"{Actions.Name(skill)}:{reason}");
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.End();
|
||||
}
|
||||
private void Check(string label, bool value, Action<bool> apply)
|
||||
{ if (ImGui.Checkbox(label, ref value)) { apply(value); Save(); } }
|
||||
private void Float(string label, float value, float min, float max, Action<float> apply)
|
||||
{ if (ImGui.SliderFloat(label, ref value, min, max, "%.2f")) { apply(value); Save(); } }
|
||||
private void Integer(string label, int value, int min, int max, Action<int> apply)
|
||||
{ if (ImGui.SliderInt(label, ref value, min, max)) { apply(value); Save(); } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
framework.Update -= runtime.Update;
|
||||
commands.RemoveHandler("/bardpvp");
|
||||
pi.UiBuilder.Draw -= Draw;
|
||||
pi.UiBuilder.OpenConfigUi -= OpenSettings;
|
||||
pi.UiBuilder.OpenMainUi -= OpenSettings;
|
||||
runtime.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user