From a54b15d1011c7f88d0f08f9f0cf7920c2a5e6661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=97=E6=B2=A2=E5=93=8D=E4=B9=9F?= <72963826+Hibiya615@users.noreply.github.com> Date: Mon, 22 Dec 2025 01:07:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=81=97=E5=BF=98=E8=A1=8C?= =?UTF-8?q?=E8=B7=AF=E9=9B=BE=E4=B9=8B=E8=BF=B9=E5=88=9D=E7=89=88=E7=BB=98?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 07-Dawntrail/Dungeon/Mistwake.cs | 775 +++++++++++++++++++++++++++++++ 1 file changed, 775 insertions(+) create mode 100644 07-Dawntrail/Dungeon/Mistwake.cs diff --git a/07-Dawntrail/Dungeon/Mistwake.cs b/07-Dawntrail/Dungeon/Mistwake.cs new file mode 100644 index 0000000..00655af --- /dev/null +++ b/07-Dawntrail/Dungeon/Mistwake.cs @@ -0,0 +1,775 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using System.Threading.Tasks; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Collections.Generic; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; + +namespace Mistwake; + +[ScriptType(guid: "5ecdb3de-a67c-46c1-bac3-20dc015363b6", name: "LV100 7.4 遗忘行路雾之迹", territorys: [1314], + version: "0.0.0.1", author: "Tetora", note: noteStr)] + +public class Mistwake +{ + const string noteStr = + """ + v0.0.0.1: + 遗忘行路雾之迹 / Mistwake / 遺忘行路 ミストウェイク 初版绘制 + """; + + #region 用户控制 + + [UserSetting("TTS开关(TTS请二选一开启)")] + public bool isTTS { get; set; } = false; + + [UserSetting("EdgeTTS开关(TTS请二选一开启)")] + public bool isEdgeTTS { get; set; } = true; + + [UserSetting("弹窗文本提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("开发者模式")] + public bool isDeveloper { get; set; } = false; + + #endregion + + #region BOSS1_特雷诺卡托布莱帕斯 / Treno catoblepas + + [ScriptMethod(name: "BOSS1_特雷诺卡托布莱帕斯 地震 AOE [Earthquake]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43327"])] + public void 地震(Event @event, ScriptAccessory accessory) + { + // if (isText)accessory.Method.TextInfo($"AOE", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"AOE"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"AOE"); + } + + [ScriptMethod(name: "BOSS1_特雷诺卡托布莱帕斯 暴雷 死刑 [Thunder III]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43329"])] + public void 特雷诺卡托布莱帕斯_暴雷(Event @event, ScriptAccessory accessory) + { + // if (isText)accessory.Method.TextInfo($"坦克死刑", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"坦克死刑"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"坦克死刑"); + } + + [ScriptMethod(name: "BOSS1_特雷诺卡托布莱帕斯 雷光射线(直线分摊)[Ray of Lightning]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44825"])] + public void 雷光射线(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"雷光射线"; + dp.Scale = new (5f, 50f); + dp.Owner = @event.SourceId(); + dp.TargetObject = @event.TargetId(); + dp.Color = accessory.Data.DefaultSafeColor; + dp.DestoryAt = 6200; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "BOSS1_特雷诺卡托布莱帕斯 震雷 分散提示 [Thunder II]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43331"])] + public void 强化寒冰咆哮(Event @event, ScriptAccessory accessory) + { + if (isText)accessory.Method.TextInfo($"分散,避开石头", duration: 2800, true); + if (isTTS)accessory.Method.TTS($"分散,避开石头"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"分散,避开石头"); + } + + [ScriptMethod(name: "BOSS1_特雷诺卡托布莱帕斯 恶魔之光 提示 [Bedeviling Light]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43330"])] + public void 恶魔之光(Event @event, ScriptAccessory accessory) + { + // if (isText)accessory.Method.TextInfo($"躲在石头后", duration: 6300, true); + if (isTTS)accessory.Method.TTS($"躲在石头后"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"躲在石头后"); + } + + [ScriptMethod(name: "BOSS1_特雷诺卡托布莱帕斯 石化吐息(顺劈)[Petribreath]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43335"])] + public void 石化吐息(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"石化吐息"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(30f); + dp.Radian = 120f.DegToRad(); + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + #endregion + + #region BOSS2_安度西亚斯 / Amdusias + + [ScriptMethod(name: "BOSS2_安度西亚斯 惊雷协奏曲(大扇形)[Thunderclap Concerto]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45337|45342)$"])] + public void 惊雷协奏曲(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"惊雷协奏曲"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(40f); + dp.Radian = 300f.DegToRad(); + dp.Rotation = @event.ActionId() == 45337 ? 0f.DegToRad() : 180f.DegToRad(); + dp.DestoryAt = 5200; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "BOSS2_安度西亚斯 猛毒菌 AOE [Bio II]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45345"])] + public void 猛毒菌(Event @event, ScriptAccessory accessory) + { + // 产生毒气团 DataId:19064 + // if (isText)accessory.Method.TextInfo($"AOE", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"AOE"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"AOE"); + } + + private readonly Queue _thunderChargeDraws = new(); + private int _drawCounter = 0; + + [ScriptMethod(name: "BOSS2_安度西亚斯 雷电飞驰(直线冲锋)[Galloping Thunder]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45348"])] + public void 雷电飞驰(Event @event, ScriptAccessory accessory) + { + var drawName = $"雷电飞驰{@event.SourceId}_{++_drawCounter}"; + _thunderChargeDraws.Enqueue(drawName); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = drawName; + dp.Owner = @event.SourceId(); + dp.TargetPosition = @event.EffectPosition(); + dp.Scale = new (5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultDangerColor.WithW(1f); + dp.DestoryAt = 15000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "雷电飞驰销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:45347"], userControl: false)] + public void 雷电飞驰销毁(Event @event, ScriptAccessory accessory) + { + if (_thunderChargeDraws.Count > 0) + { + var drawName = _thunderChargeDraws.Dequeue(); + accessory.Method.RemoveDraw(drawName); + } + } + + [ScriptMethod(name: "BOSS2_毒气团 飞散(圆形)[Burst]", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2536"])] + public void 飞散(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"飞散{@event.TargetId}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(9f); + dp.DestoryAt = 4500; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "毒气团飞散销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:45349"],userControl: false)] + public void 毒气团飞散销毁(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"飞散{@event.SourceId}"); + } + + [ScriptMethod(name: "BOSS2_安度西亚斯 霹雷 AOE+引爆 [Thunder IV]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45351"])] + public void 霹雷(Event @event, ScriptAccessory accessory) + { + // 引爆剩余毒气团【45349 飞散】 + // if (isText)accessory.Method.TextInfo($"AOE,远离剩余雷球", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"AOE,远离剩余毒球"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"AOE,远离剩余毒球"); + + foreach (var item in accessory.Data.Objects.GetByDataId(19064)) + { + if (item is KodakkuAssist.Data.IBattleChara chara) + { + if (!IbcHelper.HasStatus(accessory, chara, 0x9E8)) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "霹雷"; + dp.Owner = item.EntityId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new Vector2(9f); + dp.DestoryAt = 5700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + } + } + + [ScriptMethod(name: "BOSS2_安度西亚斯 暴雷(连续分摊)[Thunder III]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45353"])] + public void 安度西亚斯_暴雷(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"暴雷"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6f); + dp.DestoryAt = 7200; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "BOSS2_安度西亚斯 雷电震击 死刑 [Shockbolt]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45356"])] + public void 雷电震击(Event @event, ScriptAccessory accessory) + { + // if (isText)accessory.Method.TextInfo($"坦克死刑", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"坦克死刑"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"坦克死刑"); + } + + #endregion + + #region BOSS3_雷狮鹫 / Thundergust Griffin + + [ScriptMethod(name: "BOSS3_雷狮鹫 电光 AOE [Thunderspark]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45291"])] + public void 电光(Event @event, ScriptAccessory accessory) + { + // if (isText)accessory.Method.TextInfo($"AOE", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"AOE"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"AOE"); + } + + [ScriptMethod(name: "BOSS3_雷狮鹫 黄金爪 死刑 [Golden Talons]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45305"])] + public void 黄金爪(Event @event, ScriptAccessory accessory) + { + // if (isText)accessory.Method.TextInfo($"坦克死刑", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"坦克死刑"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"坦克死刑"); + } + + [ScriptMethod(name: "BOSS3_雷狮鹫 霹雳 电球直线 [Thunderbolt]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4529[678]|4694[34])$"])] + public void 霹雳(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "霹雳"; + dp.Owner = @event.SourceId(); + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.6f); + dp.Scale = new (6f, 92f); + dp.DestoryAt = 5200; + + /* + switch (@event.ActionId()) + { + case 45297: + dp.Scale = new (6f, 92f); + dp.DestoryAt = 5200; + break; + case 45298: + dp.Scale = new (6f, 92f); + dp.DestoryAt = 5200; + break; + case 46943: + dp.Scale = new (3f, 20f); + dp.DestoryAt = 4000; + break; + case 46944: + dp.Scale = new (3f, 16f); + dp.DestoryAt = 4000; + break; + } + */ + + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + + [ScriptMethod(name: "BOSS3_雷狮鹫 雷光坠击 直线击退 [Fulgurous Fall]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45301"])] + public void 雷光坠击(Event @event, ScriptAccessory accessory) + { + // 击退距离为 12m + // if (isText)accessory.Method.TextInfo($"中间击退然后躲避直线", duration: 5300, true); + if (isTTS)accessory.Method.TTS($"中间击退然后躲避直线"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"中间击退然后躲避直线"); + } + + [ScriptMethod(name: "BOSS3_雷狮鹫 雷击 二段直线提醒 [Electrogenetic Force]", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:45302"])] + public void 雷击(Event @event, ScriptAccessory accessory) + { + if (isTTS)accessory.Method.TTS($"快躲开"); + if (isEdgeTTS)accessory.Method.EdgeTTS($"快躲开"); + } + + #endregion + +} + + +public static class EventExtensions +{ + private static bool ParseHexId(string? idStr, out uint id) + { + id = 0; + if (string.IsNullOrEmpty(idStr)) return false; + try + { + var idStr2 = idStr.Replace("0x", ""); + id = uint.Parse(idStr2, System.Globalization.NumberStyles.HexNumber); + return true; + } + catch (Exception) + { + return false; + } + } + + public static uint ActionId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["ActionId"]); + } + + public static uint SourceId(this Event @event) + { + return ParseHexId(@event["SourceId"], out var id) ? id : 0; + } + + public static uint SourceDataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceDataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static byte Index(this Event @event) + { + return (byte)(ParseHexId(@event["Index"], out var index) ? index : 0); + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var state) ? state : 0; + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint TargetId(this Event @event) + { + return ParseHexId(@event["TargetId"], out var id) ? id : 0; + } + + public static Vector3 SourcePosition(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourcePosition"]); + } + + public static Vector3 TargetPosition(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetPosition"]); + } + + public static Vector3 EffectPosition(this Event @event) + { + return JsonConvert.DeserializeObject(@event["EffectPosition"]); + } + + public static uint DirectorId(this Event @event) + { + return ParseHexId(@event["DirectorId"], out var id) ? id : 0; + } + + public static uint StatusId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["StatusId"]); + } + + public static uint StackCount(this Event @event) + { + return JsonConvert.DeserializeObject(@event["StackCount"]); + } + + public static uint Param(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Param"]); + } +} + +public static class EnumExtensions +{ + public static string GetDescription(this Enum value) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = field?.GetCustomAttributes(typeof(DescriptionAttribute), false) + .FirstOrDefault() as DescriptionAttribute; + return attribute?.Description ?? value.ToString(); + } +} + +public static class MathTools +{ + public static float DegToRad(this float deg) => (deg + 360f) % 360f / 180f * float.Pi; + public static float RadToDeg(this float rad) => (rad + 2 * float.Pi) % (2 * float.Pi) / float.Pi * 180f; + + /// + /// 获得任意点与中心点的弧度值,以(0, 0, 1)方向为0,以(1, 0, 0)方向为pi/2。 + /// 即,逆时针方向增加。 + /// + /// 任意点 + /// 中心点 + /// + public static float GetRadian(this Vector3 point, Vector3 center) + => MathF.Atan2(point.X - center.X, point.Z - center.Z); + + /// + /// 获得任意点与中心点的长度。 + /// + /// 任意点 + /// 中心点 + /// + public static float GetLength(this Vector3 point, Vector3 center) + => new Vector2(point.X - center.X, point.Z - center.Z).Length(); + + /// + /// 将任意点以中心点为圆心,逆时针旋转并延长。 + /// + /// 任意点 + /// 中心点 + /// 旋转弧度 + /// 基于该点延伸长度 + /// + public static Vector3 RotateAndExtend(this Vector3 point, Vector3 center, float radian, float length) + { + var baseRad = point.GetRadian(center); + var baseLength = point.GetLength(center); + var rotRad = baseRad + radian; + return new Vector3( + center.X + MathF.Sin(rotRad) * (length + baseLength), + center.Y, + center.Z + MathF.Cos(rotRad) * (length + baseLength) + ); + } + + /// + /// 获得某角度所在划分区域 + /// + /// 输入弧度 + /// 区域划分数量 + /// 0度所在区域的初始Idx> + /// 是否为斜分割,默认为false + /// 是否顺时针增加,默认为false + /// + public static int RadianToRegion(this float radian, int regionNum, int baseRegionIdx = 0, bool isDiagDiv = false, bool isCw = false) + { + var sepRad = float.Pi * 2 / regionNum; + var inputAngle = radian * (isCw ? -1 : 1) + (isDiagDiv ? sepRad / 2 : 0); + var rad = (inputAngle + 4 * float.Pi) % (2 * float.Pi); + return ((int)Math.Floor(rad / sepRad) + baseRegionIdx + regionNum) % regionNum; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointHorizon(this Vector3 point, float centerX) + => point with { X = 2 * centerX - point.X }; + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointVertical(this Vector3 point, float centerZ) + => point with { Z = 2 * centerZ - point.Z }; + + /// + /// 将输入点中心对称 + /// + /// 输入点 + /// 中心点 + /// + public static Vector3 PointCenterSymmetry(this Vector3 point, Vector3 center) + => point.RotateAndExtend(center, float.Pi, 0); + + /// + /// 获取给定数的指定位数 + /// + /// 给定数值 + /// 对应位数,个位为1 + /// + public static int GetDecimalDigit(this int val, int x) + { + var valStr = val.ToString(); + var length = valStr.Length; + if (x < 1 || x > length) return -1; + var digitChar = valStr[length - x]; // 从右往左取第x位 + return int.Parse(digitChar.ToString()); + } +} + +public enum MarkType +{ + None = -1, + Attack1 = 0, + Attack2 = 1, + Attack3 = 2, + Attack4 = 3, + Attack5 = 4, + Bind1 = 5, + Bind2 = 6, + Bind3 = 7, + Ignore1 = 8, + Ignore2 = 9, + Square = 10, + Circle = 11, + Cross = 12, + Triangle = 13, + Attack6 = 14, + Attack7 = 15, + Attack8 = 16, + Count = 17 +} + +public static class IbcHelper +{ + public static IGameObject? GetById(this ScriptAccessory sa, ulong gameObjectId) + { + return sa.Data.Objects.SearchById(gameObjectId); + } + + public static IGameObject? GetMe(this ScriptAccessory sa) + { + return sa.Data.Objects.LocalPlayer; + } + + public static IEnumerable GetByDataId(this ScriptAccessory sa, uint dataId) + { + return sa.Data.Objects.Where(x => x.DataId == dataId); + } + + public static string GetPlayerJob(this ScriptAccessory sa, IPlayerCharacter? playerObject, bool fullName = false) + { + if (playerObject == null) return "None"; + return fullName ? playerObject.ClassJob.Value.Name.ToString() : playerObject.ClassJob.Value.Abbreviation.ToString(); + } + + public static float GetStatusRemainingTime(this ScriptAccessory sa, IBattleChara? battleChara, uint statusId) + { + if (battleChara == null || !battleChara.IsValid()) return 0; + unsafe + { + BattleChara* charaStruct = (BattleChara*)battleChara.Address; + var statusIdx = charaStruct->GetStatusManager()->GetStatusIndex(statusId); + return charaStruct->GetStatusManager()->GetRemainingTime(statusIdx); + } + } + + public static bool HasStatus(this ScriptAccessory sa, IBattleChara? battleChara, uint statusId) + { + if (battleChara == null || !battleChara.IsValid()) return false; + unsafe + { + BattleChara* charaStruct = (BattleChara*)battleChara.Address; + var statusIdx = charaStruct->GetStatusManager()->GetStatusIndex(statusId); + return statusIdx != -1; + } + } + + /// + /// 获取指定标记索引的对象EntityId + /// + public static unsafe ulong GetMarkerEntityId(uint markerIndex) + { + var markingController = MarkingController.Instance(); + if (markingController == null) return 0; + if (markerIndex >= 17) return 0; + + return markingController->Markers[(int)markerIndex]; + } + + /// + /// 获取对象身上的标记 + /// + /// MarkType + public static MarkType GetObjectMarker(IGameObject? obj) + { + if (obj == null || !obj.IsValid()) return MarkType.None; + + ulong targetEntityId = obj.EntityId; + + for (uint i = 0; i < 17; i++) + { + var markerEntityId = GetMarkerEntityId(i); + if (markerEntityId == targetEntityId) + { + return (MarkType)i; + } + } + + return MarkType.None; + } + + /// + /// 检查对象是否有指定的标记 + /// + public static bool HasMarker(IGameObject? obj, MarkType markType) + { + return GetObjectMarker(obj) == markType; + } + + /// + /// 检查对象是否有任何标记 + /// + public static bool HasAnyMarker(IGameObject? obj) + { + return GetObjectMarker(obj) != MarkType.None; + } + + private static ulong GetMarkerForObject(IGameObject? obj) + { + if (obj == null) return 0; + unsafe + { + for (uint i = 0; i < 17; i++) + { + var markerEntityId = GetMarkerEntityId(i); + if (markerEntityId == obj.EntityId) + { + return markerEntityId; + } + } + } + return 0; + } + + private static MarkType GetMarkerTypeForObject(IGameObject? obj) + { + if (obj == null) return MarkType.None; + unsafe + { + for (uint i = 0; i < 17; i++) + { + var markerEntityId = GetMarkerEntityId(i); + if (markerEntityId == obj.EntityId) + { + return (MarkType)i; + } + } + } + return MarkType.None; + } + + /// + /// 获取标记的名称 + /// + public static string GetMarkerName(MarkType markType) + { + return markType switch + { + MarkType.Attack1 => "攻击1", + MarkType.Attack2 => "攻击2", + MarkType.Attack3 => "攻击3", + MarkType.Attack4 => "攻击4", + MarkType.Attack5 => "攻击5", + MarkType.Bind1 => "止步1", + MarkType.Bind2 => "止步2", + MarkType.Bind3 => "止步3", + MarkType.Ignore1 => "禁止1", + MarkType.Ignore2 => "禁止2", + MarkType.Square => "方块", + MarkType.Circle => "圆圈", + MarkType.Cross => "十字", + MarkType.Triangle => "三角", + MarkType.Attack6 => "攻击6", + MarkType.Attack7 => "攻击7", + MarkType.Attack8 => "攻击8", + _ => "无标记" + }; + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + +} + +public static class HelperExtensions +{ + public static unsafe uint GetCurrentTerritoryId() + { + return AgentMap.Instance()->CurrentTerritoryId; // 额外进行地图ID判断 + } +} + +#region 特殊函数 +public unsafe static class ExtensionVisibleMethod +{ + public static bool IsCharacterVisible(this ICharacter chr) + { + var v = (IntPtr)(((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)chr.Address)->GameObject.DrawObject); + if (v == IntPtr.Zero) return false; + return Bitmask.IsBitSet(*(byte*)(v + 136), 0); + } + + public static class Bitmask + { + public static bool IsBitSet(ulong b, int pos) + { + return (b & (1UL << pos)) != 0; + } + + public static void SetBit(ref ulong b, int pos) + { + b |= 1UL << pos; + } + + public static void ResetBit(ref ulong b, int pos) + { + b &= ~(1UL << pos); + } + + public static bool IsBitSet(byte b, int pos) + { + return (b & (1 << pos)) != 0; + } + + public static bool IsBitSet(short b, int pos) + { + return (b & (1 << pos)) != 0; + } + } +} +#endregion 特殊函数