From c68f5f743a40c754a8557eb6d6424b67e64f64b8 Mon Sep 17 00:00:00 2001 From: NemophilaHare <1+nemophilahare@noreply.localhost> Date: Wed, 14 Oct 2026 01:17:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E5=8F=8C=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=97=A8=E6=8E=A7=E3=80=81=E7=8A=B6=E6=80=81=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E5=88=86=E7=B1=BB=E6=90=9C=E7=B4=A2=E4=B8=8EYesNo?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E8=87=AA=E5=8A=A8=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Configuration.cs | 1 + Plugin.cs | 126 ++++++++++++++++++++++++++++++++++--- Runtime/CombatModules.cs | 4 +- Runtime/MovementModules.cs | 4 +- 4 files changed, 121 insertions(+), 14 deletions(-) diff --git a/Configuration.cs b/Configuration.cs index 630337e..85bf116 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -22,6 +22,7 @@ public sealed class Configuration : IPluginConfiguration public string OpcodeUrl { get; set; } = "https://raw.githubusercontent.com/extrant/FFXIV.EXE/main/Opcode/all_opcodes.json"; public bool SmartGroundCastAtTarget { get; set; } = true; public bool FastEventFinish { get; set; } = false; + public bool FastSelectYesno { get; set; } = false; public bool AutoRetainerReventure { get; set; } = true; public float Value(int id) { diff --git a/Plugin.cs b/Plugin.cs index 6806d45..52e0297 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -64,6 +64,8 @@ public sealed class Plugin : IDalamudPlugin private long lastRetainerActionTime; private long lastAutoEventFinishTime; private long lastTalkAdvanceTime; + private long lastYesnoTime; + private string statusFilterText = ""; private uint pendingEventFinishId; private readonly IDisposable eventSubscription; @@ -206,7 +208,11 @@ public sealed class Plugin : IDalamudPlugin case "retainer" or "reventure": ProcessRetainerAutomation(); chat.Print("[IC Rebuild] 已尝试一键处理当前雇员探险窗口"); break; - default: Open(); notice="用法:/icr on|off 编号;stop;pos X Y Z;ground 技能ID [target|self];finish [事件ID];retainer"; break; + case "yes": + ConfirmYesno(true); break; + case "no": + ConfirmYesno(false); break; + default: Open(); notice="用法:/icr on|off 编号;stop;pos X Y Z;ground 技能ID [target|self];finish [事件ID];retainer;yes;no"; break; } }); } @@ -236,6 +242,13 @@ public sealed class Plugin : IDalamudPlugin var value=config.Value(feature.Id); if(sliders.Float(feature.Id==9 ? "预设高度偏移" : "参数",ref value,feature.Min,feature.Max)) { config.Values[feature.Id]=value; Try(Save); } } + if(feature.Id==4) + { + if(ImGui.SmallButton("一键完全防击退 (距离与持续时间归零)##antiKnockZero")) + { + config.Values[4]=0; config.KnockSeconds=0; Try(Save); + } + } if(feature.Id==8) DrawStatusBlock(); if(feature.Id==9) { @@ -254,6 +267,19 @@ public sealed class Plugin : IDalamudPlugin : "等待当前目标的原生半径读取。"); ImGui.TextWrapped($"目标修改 {combat.TargetRadiusChanges} · 原生圈补偿 {combat.NativeRingBonuses} · 距离检查命中 {combat.RadiusBonuses}"); } + if(feature.Id==18) + { + ImGui.TextWrapped("结印技能(天之印、地之印、人之印)复唱归零,结印无需等待 GCD。依赖模块 17。"); + } + if(feature.Id==20) + { + ImGui.Text("快速预设:"); ImGui.SameLine(); + if(ImGui.SmallButton("极限 0.5s")) { config.Values[20]=0.5f; Try(Save); } + ImGui.SameLine(); + if(ImGui.SmallButton("平滑 0.6s")) { config.Values[20]=0.6f; Try(Save); } + ImGui.SameLine(); + if(ImGui.SmallButton("默认 0.7s")) { config.Values[20]=0.7f; Try(Save); } + } ImGui.PopID(); } switch(group.Key) @@ -278,13 +304,58 @@ public sealed class Plugin : IDalamudPlugin ImGui.TextWrapped($"已选 {config.BlockedStatuses.Count} 项 · 本次启用拦截 {statusBlock.Blocked} 次 · 最近状态 ID {statusBlock.LastStatus}"); ImGui.TextWrapped("仅屏蔽本地角色后续收到的已选状态更新;开启前已有状态须等下一次更新。默认选择 1257 强制移动,空名单不屏蔽。录像回放暂停处理。"); ImGui.TextWrapped("关闭后恢复正常接收,已屏蔽状态须等服务端再次同步。本地处理不代表服务端状态被删除;实际移动或机制效果待实测。"); - if(!ImGui.CollapsingHeader("状态名单(已审计候选,效果待逐项确认)")) return; + if(!ImGui.CollapsingHeader("状态名单管理(分类预设与搜索)")) return; + + ImGui.Text("快捷预设:"); + ImGui.SameLine(); + if(ImGui.SmallButton("全选滑冰##selSlippery")) + { + foreach(var id in StatusCatalog.LegacySlipperyCandidates.Where(StatusCatalog.AuditedBlockCandidates.Contains)) + config.BlockedStatuses.Add(id); + Try(Save); + } + ImGui.SameLine(); + if(ImGui.SmallButton("全选强制移动##selMarch")) + { + foreach(var id in StatusCatalog.ForcedMarch.Where(StatusCatalog.AuditedBlockCandidates.Contains)) + config.BlockedStatuses.Add(id); + Try(Save); + } + ImGui.SameLine(); + if(ImGui.SmallButton("全选混乱##selConf")) + { + foreach(var id in StatusCatalog.Confusion.Where(StatusCatalog.AuditedBlockCandidates.Contains)) + config.BlockedStatuses.Add(id); + Try(Save); + } + ImGui.SameLine(); + if(ImGui.SmallButton("全选全部候选##selAll")) + { + foreach(var id in StatusCatalog.AuditedBlockCandidates) + config.BlockedStatuses.Add(id); + Try(Save); + } + ImGui.SameLine(); + if(ImGui.SmallButton("清空已选##clearStatus")) + { + config.BlockedStatuses.Clear(); + Try(Save); + } + + ImGui.InputText("搜索状态名称或 ID##statusSearch",ref statusFilterText,64); + var sheet=game.Data.GetExcelSheet(); foreach(var id in StatusCatalog.AuditedBlockCandidates.Order()) { - var selected=config.BlockedStatuses.Contains(id); var name=sheet?.GetRowOrDefault(id)?.Name.ToString() ?? "未知状态"; - if(ImGui.Checkbox($"{id} · {name}",ref selected)) + if(!string.IsNullOrWhiteSpace(statusFilterText)) + { + if(!id.ToString().Contains(statusFilterText,StringComparison.OrdinalIgnoreCase) && + !name.Contains(statusFilterText,StringComparison.OrdinalIgnoreCase)) + continue; + } + var selected=config.BlockedStatuses.Contains(id); + if(ImGui.Checkbox($"{id} · {name}##status{id}",ref selected)) { if(selected) config.BlockedStatuses.Add(id); else config.BlockedStatuses.Remove(id); Try(Save); @@ -454,6 +525,13 @@ public sealed class Plugin : IDalamudPlugin config.FastEventFinish = fastEv; Try(Save); } + ImGui.SameLine(); + var fastYes = config.FastSelectYesno; + if (ImGui.Checkbox("自动确认 Yes/No 提示框 (FastSelectYesno)##fastYesno", ref fastYes)) + { + config.FastSelectYesno = fastYes; + Try(Save); + } var evTimeStr = lastEventTime > 0 ? $"{(Environment.TickCount64 - lastEventTime) / 1000} 秒前" : "无"; ImGui.TextWrapped($"最近捕获事件:ID 0x{lastEventId:X8} · 目标 0x{lastEventTargetId:X8} · 参数 0x{lastEventParam:X8} · 时间 {evTimeStr}"); if (ImGui.Button("立即完成当前事件 (UP_EventFinish)")) Try(() => ExecuteEventFinish()); @@ -579,15 +657,43 @@ public sealed class Plugin : IDalamudPlugin var mgr=FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkUnitManager.Instance(); if(mgr==null) return; var now=Environment.TickCount64; - if(now-lastTalkAdvanceTime<250) return; - var talk=mgr->GetAddonByName("Talk",1); - if(talk!=null && talk->IsVisible && talk->IsReady) + if(now-lastTalkAdvanceTime>=200) + { + var talk=mgr->GetAddonByName("Talk",1); + if(talk!=null && talk->IsVisible && talk->IsReady) + { + var val=stackalloc FFXIVClientStructs.FFXIV.Component.GUI.AtkValue[1]; + val[0].SetInt(0); + talk->FireCallback(1,val,true); + lastTalkAdvanceTime=now; + } + } + if(config.FastSelectYesno && now-lastYesnoTime>=300) + { + var yesno=mgr->GetAddonByName("SelectYesno",1); + if(yesno!=null && yesno->IsVisible && yesno->IsReady) + { + var val=stackalloc FFXIVClientStructs.FFXIV.Component.GUI.AtkValue[1]; + val[0].SetInt(0); + yesno->FireCallback(1,val,true); + lastYesnoTime=now; + notice="已自动确认【SelectYesno】提示框"; + } + } + } + private unsafe void ConfirmYesno(bool yes) + { + var mgr=FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkUnitManager.Instance(); + if(mgr==null) return; + var yesno=mgr->GetAddonByName("SelectYesno",1); + if(yesno!=null && yesno->IsVisible && yesno->IsReady) { var val=stackalloc FFXIVClientStructs.FFXIV.Component.GUI.AtkValue[1]; - val[0].SetInt(0); - talk->FireCallback(1,val,true); - lastTalkAdvanceTime=now; + val[0].SetInt(yes ? 0 : 1); + yesno->FireCallback(1,val,true); + chat.Print($"[IC Rebuild] 已手动{(yes ? "确认(Yes)" : "取消(No)")}当前提示框"); } + else chat.Print("[IC Rebuild] 当前无可见的 Yes/No 提示框"); } private unsafe void ProcessRetainerAutomation() { diff --git a/Runtime/CombatModules.cs b/Runtime/CombatModules.cs index b22ed76..0c27d36 100644 --- a/Runtime/CombatModules.cs +++ b/Runtime/CombatModules.cs @@ -83,7 +83,7 @@ public sealed unsafe class CombatModules(GameContext game, Configuration config, modules.Register(13,AcquireRange); modules.Register(14, scope => { - if(game.GameVersion!=AccelerationPolicy.AuditedVersion) throw new InvalidOperationException("原生目标圈入口仅核对了 2026.09.15 客户端"); + if(!AccelerationPolicy.IsAuditedVersion(game.GameVersion)) throw new InvalidOperationException($"原生目标圈入口仅核对了已审计客户端,当前为 {game.GameVersion}。"); Hook? rangeCheck = null; Hook? precheck = null; Hook? ringUpdate=null; @@ -137,7 +137,7 @@ public sealed unsafe class CombatModules(GameContext game, Configuration config, }); modules.Register(16, scope => { - if(game.GameVersion!=AccelerationPolicy.AuditedVersion) throw new InvalidOperationException("技能位移结构仅核对了 2026.09.15 客户端"); + if(!AccelerationPolicy.IsAuditedVersion(game.GameVersion)) throw new InvalidOperationException($"技能位移结构仅核对了已审计客户端,当前为 {game.GameVersion}。"); Hook? hook = null; hook = game.Hook(scope,Signatures.ActionMovementStart,(controller,moveType,target,origin,facing,data) => { diff --git a/Runtime/MovementModules.cs b/Runtime/MovementModules.cs index 6c218a5..d02bb8e 100644 --- a/Runtime/MovementModules.cs +++ b/Runtime/MovementModules.cs @@ -39,7 +39,7 @@ public sealed unsafe class MovementModules(GameContext game, Configuration confi }); modules.Register(2, scope => { - if(game.GameVersion!=AccelerationPolicy.AuditedVersion) throw new InvalidOperationException("最大加速度仅完成 2026.09.15 客户端的字段核对。"); + if(!AccelerationPolicy.IsAuditedVersion(game.GameVersion)) throw new InvalidOperationException($"最大加速度仅完成已核对客户端的字段核对,当前为 {game.GameVersion}。"); Hook? hook = null; hook = game.Hook(scope, "40 ?? 48 ?? ?? ?? 80 79 ?? ?? 48 ?? ?? 0f 84 ?? ?? ?? ?? 48 89 7c 24 ?? 48 ?? ?? ??", actor => { @@ -85,7 +85,7 @@ public sealed unsafe class MovementModules(GameContext game, Configuration confi }); modules.Register(7, scope => { - if(game.GameVersion!=AccelerationPolicy.AuditedVersion) throw new InvalidOperationException("强制移动入口参数仅核对了 2026.09.15 客户端"); + if(!AccelerationPolicy.IsAuditedVersion(game.GameVersion)) throw new InvalidOperationException($"强制移动入口参数仅核对了已审计客户端,当前为 {game.GameVersion}。"); Hook? hook = null; hook = game.Hook(scope, Signatures.FuckBewithAction, (actor,x,y,z,movementParameter,options) => {