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 特殊函数