feat: 完善双版本门控、状态名单分类搜索与YesNo对话自动确认

This commit is contained in:
2026-10-14 01:17:24 +08:00
parent a6cccdbc43
commit c68f5f743a
4 changed files with 121 additions and 14 deletions
+1
View File
@@ -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 string OpcodeUrl { get; set; } = "https://raw.githubusercontent.com/extrant/FFXIV.EXE/main/Opcode/all_opcodes.json";
public bool SmartGroundCastAtTarget { get; set; } = true; public bool SmartGroundCastAtTarget { get; set; } = true;
public bool FastEventFinish { get; set; } = false; public bool FastEventFinish { get; set; } = false;
public bool FastSelectYesno { get; set; } = false;
public bool AutoRetainerReventure { get; set; } = true; public bool AutoRetainerReventure { get; set; } = true;
public float Value(int id) public float Value(int id)
{ {
+116 -10
View File
@@ -64,6 +64,8 @@ public sealed class Plugin : IDalamudPlugin
private long lastRetainerActionTime; private long lastRetainerActionTime;
private long lastAutoEventFinishTime; private long lastAutoEventFinishTime;
private long lastTalkAdvanceTime; private long lastTalkAdvanceTime;
private long lastYesnoTime;
private string statusFilterText = "";
private uint pendingEventFinishId; private uint pendingEventFinishId;
private readonly IDisposable eventSubscription; private readonly IDisposable eventSubscription;
@@ -206,7 +208,11 @@ public sealed class Plugin : IDalamudPlugin
case "retainer" or "reventure": case "retainer" or "reventure":
ProcessRetainerAutomation(); ProcessRetainerAutomation();
chat.Print("[IC Rebuild] 已尝试一键处理当前雇员探险窗口"); break; 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); 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(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==8) DrawStatusBlock();
if(feature.Id==9) if(feature.Id==9)
{ {
@@ -254,6 +267,19 @@ public sealed class Plugin : IDalamudPlugin
: "等待当前目标的原生半径读取。"); : "等待当前目标的原生半径读取。");
ImGui.TextWrapped($"目标修改 {combat.TargetRadiusChanges} · 原生圈补偿 {combat.NativeRingBonuses} · 距离检查命中 {combat.RadiusBonuses}"); 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(); ImGui.PopID();
} }
switch(group.Key) switch(group.Key)
@@ -278,13 +304,58 @@ public sealed class Plugin : IDalamudPlugin
ImGui.TextWrapped($"已选 {config.BlockedStatuses.Count} 项 · 本次启用拦截 {statusBlock.Blocked} 次 · 最近状态 ID {statusBlock.LastStatus}"); ImGui.TextWrapped($"已选 {config.BlockedStatuses.Count} 项 · 本次启用拦截 {statusBlock.Blocked} 次 · 最近状态 ID {statusBlock.LastStatus}");
ImGui.TextWrapped("仅屏蔽本地角色后续收到的已选状态更新;开启前已有状态须等下一次更新。默认选择 1257 强制移动,空名单不屏蔽。录像回放暂停处理。"); ImGui.TextWrapped("仅屏蔽本地角色后续收到的已选状态更新;开启前已有状态须等下一次更新。默认选择 1257 强制移动,空名单不屏蔽。录像回放暂停处理。");
ImGui.TextWrapped("关闭后恢复正常接收,已屏蔽状态须等服务端再次同步。本地处理不代表服务端状态被删除;实际移动或机制效果待实测。"); 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<Lumina.Excel.Sheets.Status>(); var sheet=game.Data.GetExcelSheet<Lumina.Excel.Sheets.Status>();
foreach(var id in StatusCatalog.AuditedBlockCandidates.Order()) foreach(var id in StatusCatalog.AuditedBlockCandidates.Order())
{ {
var selected=config.BlockedStatuses.Contains(id);
var name=sheet?.GetRowOrDefault(id)?.Name.ToString() ?? "未知状态"; 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); if(selected) config.BlockedStatuses.Add(id); else config.BlockedStatuses.Remove(id);
Try(Save); Try(Save);
@@ -454,6 +525,13 @@ public sealed class Plugin : IDalamudPlugin
config.FastEventFinish = fastEv; config.FastEventFinish = fastEv;
Try(Save); 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} 秒前" : "无"; var evTimeStr = lastEventTime > 0 ? $"{(Environment.TickCount64 - lastEventTime) / 1000} 秒前" : "无";
ImGui.TextWrapped($"最近捕获事件:ID 0x{lastEventId:X8} · 目标 0x{lastEventTargetId:X8} · 参数 0x{lastEventParam:X8} · 时间 {evTimeStr}"); ImGui.TextWrapped($"最近捕获事件:ID 0x{lastEventId:X8} · 目标 0x{lastEventTargetId:X8} · 参数 0x{lastEventParam:X8} · 时间 {evTimeStr}");
if (ImGui.Button("立即完成当前事件 (UP_EventFinish)")) Try(() => ExecuteEventFinish()); if (ImGui.Button("立即完成当前事件 (UP_EventFinish)")) Try(() => ExecuteEventFinish());
@@ -579,15 +657,43 @@ public sealed class Plugin : IDalamudPlugin
var mgr=FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkUnitManager.Instance(); var mgr=FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkUnitManager.Instance();
if(mgr==null) return; if(mgr==null) return;
var now=Environment.TickCount64; var now=Environment.TickCount64;
if(now-lastTalkAdvanceTime<250) return; if(now-lastTalkAdvanceTime>=200)
var talk=mgr->GetAddonByName("Talk",1); {
if(talk!=null && talk->IsVisible && talk->IsReady) 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]; var val=stackalloc FFXIVClientStructs.FFXIV.Component.GUI.AtkValue[1];
val[0].SetInt(0); val[0].SetInt(yes ? 0 : 1);
talk->FireCallback(1,val,true); yesno->FireCallback(1,val,true);
lastTalkAdvanceTime=now; chat.Print($"[IC Rebuild] 已手动{(yes ? "确认(Yes)" : "取消(No)")}当前提示框");
} }
else chat.Print("[IC Rebuild] 当前无可见的 Yes/No 提示框");
} }
private unsafe void ProcessRetainerAutomation() private unsafe void ProcessRetainerAutomation()
{ {
+2 -2
View File
@@ -83,7 +83,7 @@ public sealed unsafe class CombatModules(GameContext game, Configuration config,
modules.Register(13,AcquireRange); modules.Register(13,AcquireRange);
modules.Register(14, scope => 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<ActionManager.Delegates.GetActionInRangeOrLoS>? rangeCheck = null; Hook<ActionManager.Delegates.GetActionInRangeOrLoS>? rangeCheck = null;
Hook<ActionManager.Delegates.IsActionTargetInRange>? precheck = null; Hook<ActionManager.Delegates.IsActionTargetInRange>? precheck = null;
Hook<TargetRingUpdate>? ringUpdate=null; Hook<TargetRingUpdate>? ringUpdate=null;
@@ -137,7 +137,7 @@ public sealed unsafe class CombatModules(GameContext game, Configuration config,
}); });
modules.Register(16, scope => 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<Dash>? hook = null; Hook<Dash>? hook = null;
hook = game.Hook<Dash>(scope,Signatures.ActionMovementStart,(controller,moveType,target,origin,facing,data) => hook = game.Hook<Dash>(scope,Signatures.ActionMovementStart,(controller,moveType,target,origin,facing,data) =>
{ {
+2 -2
View File
@@ -39,7 +39,7 @@ public sealed unsafe class MovementModules(GameContext game, Configuration confi
}); });
modules.Register(2, scope => 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<Acceleration>? hook = null; Hook<Acceleration>? hook = null;
hook = game.Hook<Acceleration>(scope, "40 ?? 48 ?? ?? ?? 80 79 ?? ?? 48 ?? ?? 0f 84 ?? ?? ?? ?? 48 89 7c 24 ?? 48 ?? ?? ??", actor => hook = game.Hook<Acceleration>(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 => 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<Forced>? hook = null; Hook<Forced>? hook = null;
hook = game.Hook<Forced>(scope, Signatures.FuckBewithAction, (actor,x,y,z,movementParameter,options) => hook = game.Hook<Forced>(scope, Signatures.FuckBewithAction, (actor,x,y,z,movementParameter,options) =>
{ {