feat: 实现地面技能智能释放、极速交互完成与雇员探险一键重派三项业务扩展
This commit is contained in:
@@ -56,6 +56,16 @@ public sealed class Plugin : IDalamudPlugin
|
||||
private int manualSendRequestId = 1;
|
||||
private int manualTriggerCommand = 3;
|
||||
private int manualTriggerParam1 = 1;
|
||||
private int smartGroundActionId = 188;
|
||||
private uint lastEventId;
|
||||
private ulong lastEventTargetId;
|
||||
private uint lastEventParam;
|
||||
private long lastEventTime;
|
||||
private long lastRetainerActionTime;
|
||||
private long lastAutoEventFinishTime;
|
||||
private long lastTalkAdvanceTime;
|
||||
private uint pendingEventFinishId;
|
||||
private readonly IDisposable eventSubscription;
|
||||
|
||||
public Plugin(IDalamudPluginInterface pluginInterface,ICommandManager commands,IFramework framework,
|
||||
IObjectTable objects,IClientState client,ICondition conditions,IDataManager data,ISigScanner scanner,
|
||||
@@ -79,6 +89,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
});
|
||||
network=new(game,gameVersion);
|
||||
capture=new(network,modules,packets); capture.Register();
|
||||
eventSubscription=network.Pipeline.Subscribe(NetworkPhase.Received,OnNetworkReceivedForEvents);
|
||||
modules.Register(37,_ => { });
|
||||
ValidationCatalog.Apply(modules,gameVersion);
|
||||
Try(()=>
|
||||
@@ -90,14 +101,33 @@ public sealed class Plugin : IDalamudPlugin
|
||||
serviceBar=dtrBar.Get("IC Rebuild · 高度与迷宫");
|
||||
serviceBar.Shown=false;
|
||||
serviceBar.OnClick=_=>Open();
|
||||
commands.AddHandler("/icr",new CommandInfo(Command) { HelpMessage="IC Rebuild 设置。on/off 编号;stop;pos X Y Z;face 弧度;scale 倍率;export [文件名]。" });
|
||||
commands.AddHandler("/icr",new CommandInfo(Command) { HelpMessage="IC Rebuild 设置。on/off 编号;stop;pos X Y Z;ground 技能ID [target|self];finish [事件ID];retainer;face 弧度;scale 倍率;export [文件名]。" });
|
||||
pi.UiBuilder.Draw+=Draw; pi.UiBuilder.OpenConfigUi+=Open; pi.UiBuilder.OpenMainUi+=Open;
|
||||
framework.Update+=Update;
|
||||
}
|
||||
private void Open() => open=true;
|
||||
private void Update(IFramework _)
|
||||
{
|
||||
try { game.Refresh(modules.IsEnabled(10)||modules.IsEnabled(11)); game.DrainFaults(modules); combat.Update(); network.PumpActiveSends(); }
|
||||
try
|
||||
{
|
||||
game.Refresh(modules.IsEnabled(10)||modules.IsEnabled(11));
|
||||
game.DrainFaults(modules);
|
||||
combat.Update();
|
||||
network.PumpActiveSends();
|
||||
var finishId=Interlocked.Exchange(ref pendingEventFinishId,0);
|
||||
if(finishId!=0 && config.FastEventFinish)
|
||||
{
|
||||
Try(()=>
|
||||
{
|
||||
EnsureOpcodes();
|
||||
var packet=AuditedPacketEncoder.EncodeEventFinish(opcodes,gameVersion,finishId,0,0,ReadOnlySpan<uint>.Empty);
|
||||
var tx=network.EnqueueSend(packet);
|
||||
notice=$"已自动结束事件 0x{finishId:X8} (事务 #{tx.Id})";
|
||||
});
|
||||
}
|
||||
if(config.FastEventFinish) Try(ProcessFastDialogAdvance);
|
||||
if(config.AutoRetainerReventure) Try(ProcessRetainerAutomation);
|
||||
}
|
||||
catch(Exception ex) { modules.Disable(20); notice=ex.Message; log.Error(ex,"IC frame update"); }
|
||||
if(modules.IsEnabled(11))
|
||||
{
|
||||
@@ -164,7 +194,19 @@ public sealed class Plugin : IDalamudPlugin
|
||||
var exportRecords=packets.Snapshot();
|
||||
pending=Background(()=>PacketFiles.ExportAtomic(exportPath,exportRecords));
|
||||
chat.Print($"IC 开始导出 {exportRecords.Length} 条到 {exportPath}"); break;
|
||||
default: Open(); notice="用法:/icr on|off 编号;stop;pos X Y Z;face 弧度;scale 倍率"; break;
|
||||
case "ground" or "castpos" when args.Length>=2:
|
||||
var actId=uint.Parse(args[1],CultureInfo.InvariantCulture);
|
||||
var useTarget=args.Length<3 || !args[2].Equals("self",StringComparison.OrdinalIgnoreCase);
|
||||
ExecuteGroundCast(actId,useTarget); break;
|
||||
case "finish":
|
||||
uint? evId=args.Length>=2 ? (args[1].StartsWith("0x",StringComparison.OrdinalIgnoreCase)
|
||||
? uint.Parse(args[1][2..],NumberStyles.HexNumber)
|
||||
: uint.Parse(args[1],CultureInfo.InvariantCulture)) : null;
|
||||
ExecuteEventFinish(evId); break;
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -366,43 +408,120 @@ public sealed class Plugin : IDalamudPlugin
|
||||
}
|
||||
private void DrawActiveSendSection()
|
||||
{
|
||||
if(!ImGui.CollapsingHeader("主动发送与请求响应事务 (M4)")) return;
|
||||
if(!ImGui.CollapsingHeader("主动发送与业务扩展 (M4)")) return;
|
||||
var session=game.ReadSendSession();
|
||||
var dispatcher=network.ActiveSender;
|
||||
ImGui.TextWrapped($"原生发送通道:{(network.CanSendNative ? "已就绪 (ReceiverSendPacket + ZoneUp)" : "未就绪(需开启 36 或完成绑定)")} · 队列 {dispatcher.PendingQueueCount} · 等待响应 {dispatcher.InFlightCount}");
|
||||
ImGui.TextWrapped($"发送会话:{(session.IsValid ? $"就绪 · 区域 {session.Territory} · Entity 0x{session.LocalEntityId:X8}" : "未就绪(需角色在场且非过图/占卜回放)")}");
|
||||
if(opcodes.Snapshot.GameVersion!=gameVersion)
|
||||
ImGui.TextColored(new Vector4(1,0.4f,0.4f,1),$"提示:Opcode 映射尚未就绪(当前为 {opcodes.Snapshot.GameVersion});发送时将自动载入");
|
||||
ImGui.InputInt("测试技能 ID (UP_ActionSend)",ref manualSendActionId);
|
||||
ImGui.InputInt("请求序号 RequestId",ref manualSendRequestId);
|
||||
if(ImGui.Button("加入技能发送队列 (当前目标)")) Try(()=>
|
||||
|
||||
if(ImGui.CollapsingHeader("1. 地面技能智能释放 (Smart Ground Cast)", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
EnsureOpcodes();
|
||||
if(manualSendActionId<=0) throw new ArgumentException("请输入有效技能 ID");
|
||||
if(manualSendRequestId is <=0 or >ushort.MaxValue) manualSendRequestId=1;
|
||||
var targetId=game.Targets.Target?.EntityId is >0 and var tid ? (ulong)tid : session.LocalEntityId;
|
||||
var packet=AuditedPacketEncoder.EncodeActionSend(opcodes,gameVersion,(uint)manualSendActionId,1,(ushort)manualSendRequestId,targetId);
|
||||
var tx=network.EnqueueSend(packet);
|
||||
manualSendRequestId=manualSendRequestId>=ushort.MaxValue ? 1 : manualSendRequestId+1;
|
||||
notice=$"已加入发送事务 #{tx.Id}:{packet.Name} (0x{packet.Opcode:X4})";
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if(ImGui.Button("加入 ClientTrigger 队列")) Try(()=>
|
||||
{
|
||||
EnsureOpcodes();
|
||||
if(manualTriggerCommand<=0 || manualTriggerCommand>ushort.MaxValue) throw new ArgumentException("指令编号超出范围");
|
||||
var packet=AuditedPacketEncoder.EncodeClientTrigger(opcodes,gameVersion,(uint)manualTriggerCommand,(uint)Math.Max(0,manualTriggerParam1));
|
||||
var tx=network.EnqueueSend(packet);
|
||||
notice=$"已加入发送事务 #{tx.Id}:{packet.Name} (0x{packet.Opcode:X4})";
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if(ImGui.Button("清空待发队列"))
|
||||
{
|
||||
var canceled=dispatcher.CancelAllQueued("用户手动清空队列");
|
||||
notice=$"已取消 {canceled} 条待发事务";
|
||||
ImGui.TextWrapped("无需鼠标框选瞄准,通过 UP_ActionSendPos 瞬间以当前目标或自身坐标为圆心释放地面区域技能。");
|
||||
var smartTarget = config.SmartGroundCastAtTarget;
|
||||
if (ImGui.Checkbox("优先释放在当前目标位置(若无目标则释放在脚下)##smartGroundTarget", ref smartTarget))
|
||||
{
|
||||
config.SmartGroundCastAtTarget = smartTarget;
|
||||
Try(Save);
|
||||
}
|
||||
ImGui.InputInt("地面技能 ID##smartGroundAction", ref smartGroundActionId);
|
||||
if (ImGui.Button("释放地面技能 (UP_ActionSendPos)")) Try(() =>
|
||||
{
|
||||
if (smartGroundActionId <= 0) throw new ArgumentException("请输入有效技能 ID");
|
||||
ExecuteGroundCast((uint)smartGroundActionId, config.SmartGroundCastAtTarget);
|
||||
});
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("常用预设:");
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("野战治疗阵 (188)")) Try(() => { smartGroundActionId = 188; ExecuteGroundCast(188, config.SmartGroundCastAtTarget); });
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("庇护所 (3569)")) Try(() => { smartGroundActionId = 3569; ExecuteGroundCast(3569, config.SmartGroundCastAtTarget); });
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("地星 (7439)")) Try(() => { smartGroundActionId = 7439; ExecuteGroundCast(7439, config.SmartGroundCastAtTarget); });
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("土遁 (2260)")) Try(() => { smartGroundActionId = 2260; ExecuteGroundCast(2260, config.SmartGroundCastAtTarget); });
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("铃兰 (25862)")) Try(() => { smartGroundActionId = 25862; ExecuteGroundCast(25862, config.SmartGroundCastAtTarget); });
|
||||
}
|
||||
ImGui.InputInt("Trigger 指令 (ushort)",ref manualTriggerCommand);
|
||||
ImGui.InputInt("Trigger 参数 1 (uint)",ref manualTriggerParam1);
|
||||
|
||||
if(ImGui.CollapsingHeader("2. 交互/剧情极速跳过与开箱 (Fast Event Finish / Interact)", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
ImGui.TextWrapped("监听 DOWN_EventStart,直接发送 UP_EventFinish 完成 NPC 对话、开宝箱、副本传送门与剧情过渡。");
|
||||
var fastEv = config.FastEventFinish;
|
||||
if (ImGui.Checkbox("自动极速结束交互与对话 (FastEventFinish)##fastEventFinish", ref fastEv))
|
||||
{
|
||||
config.FastEventFinish = fastEv;
|
||||
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());
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("与当前目标发起事件 (UP_EventStart)")) Try(() =>
|
||||
{
|
||||
EnsureOpcodes();
|
||||
var targetId = game.Targets.Target?.EntityId is > 0 and var tid ? (ulong)tid : 0xE0000000;
|
||||
if (targetId == 0xE0000000) throw new ArgumentException("请先选中一个可交互的目标");
|
||||
var evId = lastEventId != 0 ? lastEventId : 1;
|
||||
var packet = AuditedPacketEncoder.EncodeEventStart(opcodes, gameVersion, targetId, evId);
|
||||
var tx = network.EnqueueSend(packet);
|
||||
notice = $"已发送事件发起 #{tx.Id}:目标 0x{targetId:X8} 事件 0x{evId:X8}";
|
||||
});
|
||||
}
|
||||
|
||||
if(ImGui.CollapsingHeader("3. 生活自动化:雇员一键重派探险 (Retainer Re-Venture)", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
ImGui.TextWrapped("打开雇员探险结算界面时,自动或一键触发【再次派遣】与【确认】,省去重复繁琐的鼠标点击。");
|
||||
var autoRet = config.AutoRetainerReventure;
|
||||
if (ImGui.Checkbox("自动重派雇员探险 (AutoRetainerReventure)##autoRetainer", ref autoRet))
|
||||
{
|
||||
config.AutoRetainerReventure = autoRet;
|
||||
Try(Save);
|
||||
}
|
||||
if (ImGui.Button("一键重派当前雇员探险窗口")) Try(() =>
|
||||
{
|
||||
ProcessRetainerAutomation();
|
||||
notice = "已尝试处理当前雇员探险窗口";
|
||||
});
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(new Vector4(0.4f, 1, 0.4f, 1), "状态:安全防抖 450ms 原生调用");
|
||||
}
|
||||
|
||||
if(ImGui.CollapsingHeader("基础指令与发包诊断"))
|
||||
{
|
||||
ImGui.InputInt("测试技能 ID (UP_ActionSend)",ref manualSendActionId);
|
||||
ImGui.InputInt("请求序号 RequestId",ref manualSendRequestId);
|
||||
if(ImGui.Button("加入技能发送队列 (当前目标)")) Try(()=>
|
||||
{
|
||||
EnsureOpcodes();
|
||||
if(manualSendActionId<=0) throw new ArgumentException("请输入有效技能 ID");
|
||||
if(manualSendRequestId is <=0 or >ushort.MaxValue) manualSendRequestId=1;
|
||||
var targetId=game.Targets.Target?.EntityId is >0 and var tid ? (ulong)tid : session.LocalEntityId;
|
||||
var packet=AuditedPacketEncoder.EncodeActionSend(opcodes,gameVersion,(uint)manualSendActionId,1,(ushort)manualSendRequestId,targetId);
|
||||
var tx=network.EnqueueSend(packet);
|
||||
manualSendRequestId=manualSendRequestId>=ushort.MaxValue ? 1 : manualSendRequestId+1;
|
||||
notice=$"已加入发送事务 #{tx.Id}:{packet.Name} (0x{packet.Opcode:X4})";
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if(ImGui.Button("加入 ClientTrigger 队列")) Try(()=>
|
||||
{
|
||||
EnsureOpcodes();
|
||||
if(manualTriggerCommand<=0 || manualTriggerCommand>ushort.MaxValue) throw new ArgumentException("指令编号超出范围");
|
||||
var packet=AuditedPacketEncoder.EncodeClientTrigger(opcodes,gameVersion,(uint)manualTriggerCommand,(uint)Math.Max(0,manualTriggerParam1));
|
||||
var tx=network.EnqueueSend(packet);
|
||||
notice=$"已加入发送事务 #{tx.Id}:{packet.Name} (0x{packet.Opcode:X4})";
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if(ImGui.Button("清空待发队列"))
|
||||
{
|
||||
var canceled=dispatcher.CancelAllQueued("用户手动清空队列");
|
||||
notice=$"已取消 {canceled} 条待发事务";
|
||||
}
|
||||
ImGui.InputInt("Trigger 指令 (ushort)",ref manualTriggerCommand);
|
||||
ImGui.InputInt("Trigger 参数 1 (uint)",ref manualTriggerParam1);
|
||||
}
|
||||
|
||||
foreach(var tx in dispatcher.Snapshot().TakeLast(10))
|
||||
{
|
||||
var rtt=tx.RoundTripMs is {} d ? $" · RTT {d} ms (下行 #{tx.ResponseObservationId})" : "";
|
||||
@@ -410,6 +529,92 @@ public sealed class Plugin : IDalamudPlugin
|
||||
ImGui.TextWrapped($"事务 #{tx.Id} {tx.PacketName} (0x{tx.Opcode:X4}) [{tx.State}]{obs}{rtt} · {tx.Detail}");
|
||||
}
|
||||
}
|
||||
private void OnNetworkReceivedForEvents(NetworkObservation obs)
|
||||
{
|
||||
var msg=obs.Message;
|
||||
if(!msg.Complete || msg.Direction!=PacketDirection.Down) return;
|
||||
var downEventStartOpcode=opcodes.ResolveOpcode(gameVersion,PacketDirection.Down,"DOWN_EventStart");
|
||||
if(downEventStartOpcode.HasValue && msg.Opcode==downEventStartOpcode.Value && msg.Payload.Length>=0x10)
|
||||
{
|
||||
lastEventTargetId=System.Buffers.Binary.BinaryPrimitives.ReadUInt64LittleEndian(msg.Payload[..8]);
|
||||
lastEventId=System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(msg.Payload.Slice(8,4));
|
||||
lastEventParam=System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(msg.Payload.Slice(12,4));
|
||||
lastEventTime=Environment.TickCount64;
|
||||
if(config.FastEventFinish && lastEventId!=0 && Environment.TickCount64-lastAutoEventFinishTime>=800)
|
||||
{
|
||||
lastAutoEventFinishTime=Environment.TickCount64;
|
||||
Volatile.Write(ref pendingEventFinishId,lastEventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void ExecuteGroundCast(uint actionId,bool atTarget)
|
||||
{
|
||||
EnsureOpcodes();
|
||||
if(actionId==0) throw new ArgumentException("请输入有效地面技能 ID");
|
||||
var session=game.ReadSendSession();
|
||||
if(!session.IsValid) throw new InvalidOperationException("当前角色或区域会话未就绪");
|
||||
var player=game.Objects.LocalPlayer;
|
||||
if(player==null) throw new InvalidOperationException("本地角色未就绪");
|
||||
Vector3 targetPos=atTarget && game.Targets.Target is { } target ? target.Position : player.Position;
|
||||
ushort facing=(ushort)((player.Rotation+MathF.PI)/(MathF.PI*2f)*ushort.MaxValue);
|
||||
manualSendRequestId=manualSendRequestId>=ushort.MaxValue ? 1 : manualSendRequestId+1;
|
||||
var packet=AuditedPacketEncoder.EncodeActionSendPos(opcodes,gameVersion,actionId,1,(ushort)manualSendRequestId,targetPos,facing);
|
||||
var tx=network.EnqueueSend(packet);
|
||||
var destStr=atTarget && game.Targets.Target!=null ? $"目标【{game.Targets.Target.Name}】" : "自身脚下";
|
||||
notice=$"已加入地面技能事务 #{tx.Id}:技能 {actionId} -> {destStr} ({targetPos.X:F1}, {targetPos.Y:F1}, {targetPos.Z:F1})";
|
||||
chat.Print($"[IC Rebuild] 地面技能 {actionId} 已排队发送至 {destStr}");
|
||||
}
|
||||
private void ExecuteEventFinish(uint? explicitEventId=null)
|
||||
{
|
||||
EnsureOpcodes();
|
||||
var targetEventId=explicitEventId ?? lastEventId;
|
||||
if(targetEventId==0) throw new InvalidOperationException("当前无可用事件 ID,请先触发交互或传入目标事件 ID");
|
||||
var packet=AuditedPacketEncoder.EncodeEventFinish(opcodes,gameVersion,targetEventId,0,0,ReadOnlySpan<uint>.Empty);
|
||||
var tx=network.EnqueueSend(packet);
|
||||
notice=$"已加入事件完成事务 #{tx.Id}:事件 ID 0x{targetEventId:X8}";
|
||||
chat.Print($"[IC Rebuild] 立即完成事件 0x{targetEventId:X8} 已排队发送");
|
||||
}
|
||||
private unsafe void ProcessFastDialogAdvance()
|
||||
{
|
||||
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)
|
||||
{
|
||||
var val=stackalloc FFXIVClientStructs.FFXIV.Component.GUI.AtkValue[1];
|
||||
val[0].SetInt(0);
|
||||
talk->FireCallback(1,val,true);
|
||||
lastTalkAdvanceTime=now;
|
||||
}
|
||||
}
|
||||
private unsafe void ProcessRetainerAutomation()
|
||||
{
|
||||
var mgr=FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkUnitManager.Instance();
|
||||
if(mgr==null) return;
|
||||
var now=Environment.TickCount64;
|
||||
if(now-lastRetainerActionTime<450) return;
|
||||
var resultAddon=mgr->GetAddonByName("RetainerTaskResult",1);
|
||||
if(resultAddon!=null && resultAddon->IsVisible && resultAddon->IsReady)
|
||||
{
|
||||
var val=stackalloc FFXIVClientStructs.FFXIV.Component.GUI.AtkValue[1];
|
||||
val[0].SetInt(0);
|
||||
resultAddon->FireCallback(1,val,true);
|
||||
lastRetainerActionTime=now;
|
||||
notice="已自动执行雇员探险【再次派遣】";
|
||||
return;
|
||||
}
|
||||
var askAddon=mgr->GetAddonByName("RetainerTaskAsk",1);
|
||||
if(askAddon!=null && askAddon->IsVisible && askAddon->IsReady)
|
||||
{
|
||||
var val=stackalloc FFXIVClientStructs.FFXIV.Component.GUI.AtkValue[1];
|
||||
val[0].SetInt(0);
|
||||
askAddon->FireCallback(1,val,true);
|
||||
lastRetainerActionTime=now;
|
||||
notice="已自动执行雇员探险【确定派遣】";
|
||||
}
|
||||
}
|
||||
private string DataPath(string name)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(name) || Path.GetFileName(name)!=name || name.IndexOfAny(Path.GetInvalidFileNameChars())>=0) throw new ArgumentException("请输入当前记录目录中的文件名");
|
||||
@@ -468,6 +673,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
framework.Update-=Update; commands.RemoveHandler("/icr");
|
||||
pi.UiBuilder.Draw-=Draw; pi.UiBuilder.OpenConfigUi-=Open; pi.UiBuilder.OpenMainUi-=Open;
|
||||
serviceBar.OnClick=null; serviceBar.Dispose();
|
||||
eventSubscription.Dispose();
|
||||
modules.Dispose(); network.Dispose(); game.Frames.Clear(); cancellation.Cancel(); http.Dispose(); cancellation.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user