From 50e16c40b0e8a15a6b970d8819a1def04027e7ba Mon Sep 17 00:00:00 2001 From: VeeverSW Date: Sun, 13 Sep 2026 19:33:02 +0800 Subject: [PATCH 1/4] feat: Push Veever's repo --- VeeverSW/00-Other/Cliffhanger.cs | 200 + VeeverSW/00-Other/VVToolKit.cs | 2032 +++++++++ VeeverSW/02-A-Realm-Reborn/Guildhests.cs | 3215 +++++++++++++++ VeeverSW/02-A-Realm-Reborn/Haukke-Manor.cs | 791 ++++ VeeverSW/02-A-Realm-Reborn/Snowcloak.cs | 2134 ++++++++++ VeeverSW/03-Heavensward/A11N.cs | 197 + VeeverSW/03-Heavensward/A12N.cs | 207 + VeeverSW/03-Heavensward/A9N.cs | 131 + .../the-Aetherochemical-Research-Facility.cs | 734 ++++ .../the-Great-Gubal-Library-Hard.cs | 1795 ++++++++ VeeverSW/04-Stormblood/empty.md | 0 VeeverSW/05-Shadowbringers/DohnMheg.cs | 276 ++ .../Edens-Verse-Iconoclasm.cs | 752 ++++ .../05-Shadowbringers/HolminsterSwitch.cs | 308 ++ VeeverSW/05-Shadowbringers/Malikah-s-Well.cs | 1882 +++++++++ VeeverSW/05-Shadowbringers/MtGulg.cs | 1975 +++++++++ .../05-Shadowbringers/the-Grand-Cosmos.cs | 1087 +++++ .../05-Shadowbringers/the-Qitana-Ravel.cs | 949 +++++ .../the-Seat-of-Sacrifice.cs | 2090 ++++++++++ VeeverSW/06-EndWalker/the-Dead-Ends.cs | 1813 +++++++++ VeeverSW/06-EndWalker/the-Final-Day.cs | 1601 ++++++++ .../the-Minstrels-Ballad-Endsingers-Aria.cs | 291 ++ VeeverSW/06-EndWalker/the-Tower-of-Zot.cs | 600 +++ VeeverSW/07-DawnTrail/Cenote-Ja-Ja-Gural.cs | 313 ++ VeeverSW/07-DawnTrail/Everkeep.cs | 711 ++++ VeeverSW/07-DawnTrail/Hells-Kier-Unreal.cs | 1236 ++++++ VeeverSW/07-DawnTrail/Mistwake.cs | 2456 +++++++++++ .../San-d-Oria-The-Second-Walk.cs | 2983 ++++++++++++++ VeeverSW/07-DawnTrail/The-Jade-Stoa-Unreal.cs | 898 ++++ VeeverSW/07-DawnTrail/TheWindwardWilds.cs | 2428 +++++++++++ .../07-DawnTrail/TheWindwardWildsExtreme.cs | 3614 +++++++++++++++++ VeeverSW/07-DawnTrail/Vault-Oneiron.cs | 2329 +++++++++++ .../07-DawnTrail/Yuweyawata-Field-Station.cs | 1047 +++++ VeeverSW/07-DawnTrail/the-Clyteum.cs | 2181 ++++++++++ VeeverSW/07-DawnTrail/the-Meso-Terminal.cs | 1883 +++++++++ VeeverSW/07-DawnTrail/the-Underkeep.cs | 861 ++++ .../the-Wreath-of-Snakes-Unreal.cs | 1956 +++++++++ 37 files changed, 49956 insertions(+) create mode 100644 VeeverSW/00-Other/Cliffhanger.cs create mode 100644 VeeverSW/00-Other/VVToolKit.cs create mode 100644 VeeverSW/02-A-Realm-Reborn/Guildhests.cs create mode 100644 VeeverSW/02-A-Realm-Reborn/Haukke-Manor.cs create mode 100644 VeeverSW/02-A-Realm-Reborn/Snowcloak.cs create mode 100644 VeeverSW/03-Heavensward/A11N.cs create mode 100644 VeeverSW/03-Heavensward/A12N.cs create mode 100644 VeeverSW/03-Heavensward/A9N.cs create mode 100644 VeeverSW/03-Heavensward/the-Aetherochemical-Research-Facility.cs create mode 100644 VeeverSW/03-Heavensward/the-Great-Gubal-Library-Hard.cs create mode 100644 VeeverSW/04-Stormblood/empty.md create mode 100644 VeeverSW/05-Shadowbringers/DohnMheg.cs create mode 100644 VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs create mode 100644 VeeverSW/05-Shadowbringers/HolminsterSwitch.cs create mode 100644 VeeverSW/05-Shadowbringers/Malikah-s-Well.cs create mode 100644 VeeverSW/05-Shadowbringers/MtGulg.cs create mode 100644 VeeverSW/05-Shadowbringers/the-Grand-Cosmos.cs create mode 100644 VeeverSW/05-Shadowbringers/the-Qitana-Ravel.cs create mode 100644 VeeverSW/05-Shadowbringers/the-Seat-of-Sacrifice.cs create mode 100644 VeeverSW/06-EndWalker/the-Dead-Ends.cs create mode 100644 VeeverSW/06-EndWalker/the-Final-Day.cs create mode 100644 VeeverSW/06-EndWalker/the-Minstrels-Ballad-Endsingers-Aria.cs create mode 100644 VeeverSW/06-EndWalker/the-Tower-of-Zot.cs create mode 100644 VeeverSW/07-DawnTrail/Cenote-Ja-Ja-Gural.cs create mode 100644 VeeverSW/07-DawnTrail/Everkeep.cs create mode 100644 VeeverSW/07-DawnTrail/Hells-Kier-Unreal.cs create mode 100644 VeeverSW/07-DawnTrail/Mistwake.cs create mode 100644 VeeverSW/07-DawnTrail/San-d-Oria-The-Second-Walk.cs create mode 100644 VeeverSW/07-DawnTrail/The-Jade-Stoa-Unreal.cs create mode 100644 VeeverSW/07-DawnTrail/TheWindwardWilds.cs create mode 100644 VeeverSW/07-DawnTrail/TheWindwardWildsExtreme.cs create mode 100644 VeeverSW/07-DawnTrail/Vault-Oneiron.cs create mode 100644 VeeverSW/07-DawnTrail/Yuweyawata-Field-Station.cs create mode 100644 VeeverSW/07-DawnTrail/the-Clyteum.cs create mode 100644 VeeverSW/07-DawnTrail/the-Meso-Terminal.cs create mode 100644 VeeverSW/07-DawnTrail/the-Underkeep.cs create mode 100644 VeeverSW/07-DawnTrail/the-Wreath-of-Snakes-Unreal.cs diff --git a/VeeverSW/00-Other/Cliffhanger.cs b/VeeverSW/00-Other/Cliffhanger.cs new file mode 100644 index 0000000..6ed721e --- /dev/null +++ b/VeeverSW/00-Other/Cliffhanger.cs @@ -0,0 +1,200 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; + +namespace Veever.Other.Cliffhanger; + +[ScriptType(name: "金碟-小雏鸟的营救者", territorys: [144], guid: "4359482a-b7a8-4eb1-9a42-ffa3c79d6b7b", + version: "0.0.0.3", author: "Veever", note: noteStr)] + +public class Cliffhanger +{ + const string noteStr = + """ + v0.0.0.3: + 1. 支持经典版小雏鸟的营救者 + 2. 炸弹爆炸范围和时间是精准的,请参考画图 + 3. TODO: 另一版本小雏鸟的营救者(抽到就做) + 4. 金碟!!碟!!!!! + 鸭门。 + """; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + accessory.Method.MarkClear(); + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "炸弹", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:3782"])] + public void bomb(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "炸弹"; + //dp.Color = accessory.Data.DefaultDangerColor; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = @event.SourcePosition(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(2); + dp.DestoryAt = 3800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } +} + + +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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + if (isDRTTS) + { + accessory.Method.SendChat($"/pdr tts {text}"); + } + else if (isTTS) + { + accessory.Method.TTS(text); + } + } +} + diff --git a/VeeverSW/00-Other/VVToolKit.cs b/VeeverSW/00-Other/VVToolKit.cs new file mode 100644 index 0000000..ae4de5b --- /dev/null +++ b/VeeverSW/00-Other/VVToolKit.cs @@ -0,0 +1,2032 @@ +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.System.Input; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Common.Lua; +using FFXIVClientStructs.FFXIV.Component.GUI; +using InteropGenerator.Runtime.Attributes; +using System.Management; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.Draw.Vfx.VfxNative; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data; +using Lumina.Excel.Sheets; +using Lumina.Excel.Sheets.Experimental; +using Newtonsoft.Json; +using System; +using System; +using System.Collections.Generic; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Linq; +using System.Net.Http; +using System.Net.Http; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Xml.Linq; +using static FFXIVClientStructs.FFXIV.Client.Game.ActionManager.Delegates; +using static FFXIVClientStructs.FFXIV.Client.UI.Misc.DataCenterHelper; +using static KodakkuAssist.Script.ScriptAccessory; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + + +namespace Veever.Other.VVToolKit; + +[ScriptType(name: Name, territorys: [], guid: "260323f1-9d7d-4fd6-9222-282eb1aa9bf5", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +public class VVToolKit +{ + const string NoteStr = + $""" + v{Version} + --------------------------------------------------------- + 此工具箱已修改为dev辅助性工具,删除了之前的大部分功能,如无需要请删除 + 1. /e vvguid 自动生成新的guid并复制到剪切板 + 2. /e vvlist 列出附近最多10个玩家 + 鸭门 + """; + + const string UpdateInfo = + $""" + v{Version} + 删除了更新提醒的网络报错提示,为版本更新提示加上了脚本名字和作者名字 + 鸭门 + ---------------------------------- + Deleted the network error message for update checking, added script name and author to update notification. + Duckmen. + """; + + private const string Name = "vv工具箱"; + private const string Version = "0.0.3.1"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = false; + + [UserSetting("是否将结果输出至聊天栏")] + public bool isChat { get; set; } = false; + + public string name = ""; + + private readonly object findLock = new object(); + private List cachedPlayers = new(); + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isChat) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + sa.Method.ClearFrameworkUpdateAction(this); + name = ""; + _ = ScriptVersionChecker.CheckVersionAsync( + sa, + "260323f1-9d7d-4fd6-9222-282eb1aa9bf5", + Version, + showNotification: true + ); + sa.Log.Debug($"Id:{HelperExtensions.GetCurrentTerritoryId()}"); + } + + + + [ScriptMethod(name: "列出附近玩家", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:regex:^vvlist$"])] + public async void listPlayers(Event ev, ScriptAccessory sa) + { + var myobj = sa.Data.MyObject; + if (myobj == null) + { + sa.Method.SendChat("/e 无法获取玩家位置"); + return; + } + + lock (findLock) + { + cachedPlayers = sa.Data.Objects? + .Where(x => x.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Pc) + .OrderBy(x => Vector3.Distance(myobj.Position, x.Position)) + .Take(10) + .ToList() ?? new(); + } + + if (cachedPlayers.Count == 0) + { + sa.Method.SendChat("/e 未找到附近的玩家"); + return; + } + + sa.Method.SendChat("/e === 附近的玩家 (使用 /e vvselect <编号> 选择) ==="); + await Task.Delay(50); + + for (int i = 0; i < cachedPlayers.Count; i++) + { + var distance = Vector3.Distance(myobj.Position, cachedPlayers[i].Position); + sa.Method.SendChat($"/e [{i + 1}] {cachedPlayers[i].Name} ({distance:F1}m)"); + await Task.Delay(50); + } + + sa.Log.Debug($"已列出 {cachedPlayers.Count} 个附近的玩家"); + } + + [ScriptMethod(name: "vvguid", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:regex:^vvguid$"])] + public void vvGuid(Event ev, ScriptAccessory sa) + { + Guid guid = Guid.NewGuid(); + string guidString = guid.ToString(); + + bool success = false; + try + { + var thread = new Thread(() => + { + try + { + System.Windows.Forms.Clipboard.SetText(guidString); + success = true; + } + catch (Exception ex) + { + sa.Log.Error($"剪切板线程内部错误: {ex.Message}"); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + + if (success) + { + sa.Method.SendChat($"/e {guidString} (已复制到剪切板)"); + sa.Log.Debug($"GUID 已复制到剪切板: {guidString}"); + } + else + { + sa.Method.SendChat($"/e {guidString} (复制失败)"); + } + } + catch (Exception ex) + { + sa.Method.SendChat($"/e {guidString} (复制失败)"); + sa.Log.Error($"复制到剪切板失败: {ex.Message}"); + } + } + + [ScriptMethod(name: "vvtest", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:regex:^vvtest$"])] + public void vvTest(Event ev, ScriptAccessory sa) + { + //var vfxpath = "vfx/common/eff/targetlockon_stlp01_c0o2.avfx"; + //var myobj = sa.Data.MyObject; + //if (myobj == null) return; + + //sa.Method.VfxMethod.CreateLockOn(23, sa.Data.MyObject.EntityId); + + //sa.Method.UseAction(sa.Data.Me, 7548); + + + + //scale: new Vector3(enlarge by X, height, enlarge by Z) + var mypos = sa.Data.MyObject.Position; + mypos.Y += 10; + mypos.Z += 5; + sa.Log.Debug($"玩家位置: {mypos}"); + sa.Method.VfxMethod.CreateOmen(335, new Vector3(2, 3, 2), mypos, 0); + //sa.Method.RunOnMainThreadAsync(() => + //{ + // sa.Method.VfxMethod.SetVfxSpeed(); + //}); + // Omen + // 27 核爆 390-391 + // 65-90-120 踩塔 带特效: 239- 638(start multi) + // 335 双人踩塔 336 三人踩塔 337 四人 + // 453 分摊踩塔 + // 467 死刑踩塔 + // 540 双人死刑踩塔 + // 176 十字 + // 574-575-576 三角形 + + // lockon + // 72 分摊 + //sa.Log.Debug($"{sa.Method.hitboxRadius(sa.Data.MyObject)}"); + } + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战物理) / "Ranged Physical DPS"(远程物理) / "Ranged Magical DPS"(远程魔法) / "Unknown" / "None" + /// 大版本更新需要维护 + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + + return playerObject.ClassJob.RowId switch + { + // Tank: 骑士、战士、暗黑骑士、绝枪战士 + 19 or 21 or 32 or 37 => "Tank", + // Healer: 白魔法师、学者、占星术士、贤者 + 24 or 28 or 33 or 40 => "Healer", + // Melee DPS: 武僧、龙骑士、忍者、武士、钐镰客、蝰蛇剑士 + 20 or 22 or 30 or 34 or 39 or 41 => "Melee DPS", + // Ranged Physical DPS: 吟游诗人、机工士、舞者 + 23 or 31 or 38 => "Ranged Physical DPS", + // Ranged Magical DPS: 黑魔法师、召唤师、赤魔法师、青魔法师、绘灵法师 + 25 or 27 or 35 or 36 or 42 => "Ranged Magical DPS", + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + public static string GetObjectName(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return "None"; + return obj.Name.ToString(); + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } + + [Flags] + public enum DrawState : uint + { + Invisibility = 0x00_00_00_02, + IsLoading = 0x00_00_08_00, + SomeNpcFlag = 0x00_00_01_00, + MaybeCulled = 0x00_00_04_00, + MaybeHiddenMinion = 0x00_00_80_00, + MaybeHiddenSummon = 0x00_80_00_00, + } + + public static unsafe DrawState* ActorDrawState(IGameObject actor) + => (DrawState*)(&((GameObject*)actor.Address)->RenderFlags); + + /// + /// 检查对象可见性(Read) + /// + /// ScriptAccessory + /// Obj need check + /// true=检查可见,false=检查不可见 + /// 如果符合检查条件返回 True + public static unsafe bool IsActorVisible(this ScriptAccessory sa, IGameObject? obj, bool checkVisible = true) + { + if (obj == null) return false; + + try + { + var state = *ActorDrawState(obj); + bool isVisible = (state & DrawState.Invisibility) == 0; + + return checkVisible ? isVisible : !isVisible; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } + + /// + /// 设置对象可见性(Write) + /// + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + if (actor == null) return; + + try + { + var statePtr = ActorDrawState(actor); + if (visible) + *statePtr &= ~DrawState.Invisibility; + else + *statePtr |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + throw; + } + } + + /// + /// Check DrawState + /// + public static unsafe bool HasDrawState(this ScriptAccessory sa, IGameObject? actor, DrawState state) + { + if (actor == null) return false; + + try + { + return (*ActorDrawState(actor) & state) != 0; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float? rotation = null, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosTarget(ScriptAccessory accessory, Vector3 pos, Vector3 targetpos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.TargetPosition = targetpos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTargetPos(ScriptAccessory accessory, ulong owner, Vector3 targetPos, Vector2 scale, int duration, string name, + Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObjectPos(ScriptAccessory accessory, ulong obj, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = obj; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class Extensions +{ + public static void antiKnockBack(this MethodAccessory method, ScriptAccessory sa) + { + // 7559 沉稳咏唱 + // 7548 亲疏自行 + var myobj = sa.Data.MyObject; + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Tank" || IbcHelper.GetPlayerRole(sa, myobj) == "Melee DPS" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Physical DPS") + { + method.UseAction(myobj.EntityId, 7548); + } + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Healer" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Magical DPS") + { + method.UseAction(myobj.EntityId, 7559); + } + } + public static float hitboxRadius(this MethodAccessory method, IPlayerCharacter obj) + { + return obj.HitboxRadius; + } +} + +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 + +#region 脚本版本检查 +public static class ScriptVersionChecker +{ + private const string OnlineRepoUrl = "https://raw.githubusercontent.com/VeeverSW/Kodakku-Script/refs/heads/main/OnlineRepo.json"; + private static readonly HttpClient _httpClient = new HttpClient(); + + /// + /// 在线仓库脚本信息 + /// + public class OnlineScriptInfo + { + public string Name { get; set; } = ""; + public string Guid { get; set; } = ""; + public string Version { get; set; } = ""; + public string Author { get; set; } = ""; + public string Repo { get; set; } = ""; + public string DownloadUrl { get; set; } = ""; + public string Note { get; set; } = ""; + public string UpdateInfo { get; set; } = ""; + public int[] TerritoryIds { get; set; } = Array.Empty(); + } + + /// + /// 版本 + /// + public enum VersionCompareResult + { + /// 当前版本较新或相同 + UpToDate, + /// 有新版本可用 + UpdateAvailable, + /// 未找到匹配的脚本 + NotFound, + /// 检查失败 + Error + } + + /// + /// 检查脚本版本 + /// + /// ScriptAccessory + /// 脚本GUID + /// 当前版本号 + /// 是否显示通知 + /// 版本比较结果 + public static async Task<(VersionCompareResult result, OnlineScriptInfo? onlineInfo)> CheckVersionAsync( + ScriptAccessory sa, + string guid, + string currentVersion, + bool showNotification = true) + { + try + { + sa.Log.Debug($"开始检查脚本版本 (GUID: {guid}, 当前版本: {currentVersion})"); + + var response = await _httpClient.GetStringAsync(OnlineRepoUrl); + var onlineScripts = JsonConvert.DeserializeObject>(response); + + if (onlineScripts == null || onlineScripts.Count == 0) + { + sa.Log.Error("无法解析在线仓库数据"); + return (VersionCompareResult.Error, null); + } + + var onlineScript = onlineScripts.FirstOrDefault(s => + s.Guid.Equals(guid, StringComparison.OrdinalIgnoreCase)); + + if (onlineScript == null) + { + //sa.Log.Debug($"在线仓库中未找到 GUID 为 {guid} 的脚本"); + //if (showNotification) + //{ + // sa.Method.TextInfo("该脚本未在在线仓库中注册", 3000); + //} + return (VersionCompareResult.NotFound, null); + } + + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 找到在线脚本: {onlineScript.Name}, 在线版本: {onlineScript.Version}"); + + var compareResult = CompareVersions(currentVersion, onlineScript.Version); + + if (compareResult < 0) + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新 (当前: {currentVersion})"); + if (showNotification) + { + sa.Method.TextInfo( + $"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新\n当前版本: {currentVersion}", + 5000, + true); + } + return (VersionCompareResult.UpdateAvailable, onlineScript); + } + else + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 当前版本已是最新 (当前: {currentVersion}, 在线: {onlineScript.Version})"); + + return (VersionCompareResult.UpToDate, onlineScript); + } + } + catch (HttpRequestException ex) + { + //sa.Log.Error($"网络请求失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败: 网络错误", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + catch (Exception ex) + { + //sa.Log.Error($"版本检查失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + } + + /// + /// 比较版本号 + /// + /// 版本1 (例如: "0.0.0.3") + /// 版本2 (例如: "0.0.0.5") + /// 负数: version1 < version2, 0: 相等, 正数: version1 > version2 + private static int CompareVersions(string version1, string version2) + { + var v1Parts = version1.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + var v2Parts = version2.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + + int maxLength = Math.Max(v1Parts.Length, v2Parts.Length); + + for (int i = 0; i < maxLength; i++) + { + int v1Part = i < v1Parts.Length ? v1Parts[i] : 0; + int v2Part = i < v2Parts.Length ? v2Parts[i] : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } +} +#endregion + +public static class HelperExtensions +{ + public static unsafe uint GetCurrentTerritoryId() + { + return AgentMap.Instance()->CurrentTerritoryId; + } +} diff --git a/VeeverSW/02-A-Realm-Reborn/Guildhests.cs b/VeeverSW/02-A-Realm-Reborn/Guildhests.cs new file mode 100644 index 0000000..520e30d --- /dev/null +++ b/VeeverSW/02-A-Realm-Reborn/Guildhests.cs @@ -0,0 +1,3215 @@ +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.System.Input; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Common.Lua; +using FFXIVClientStructs.FFXIV.Component.GUI; +using InteropGenerator.Runtime.Attributes; +using System.Management; +using System.Reflection.Metadata; +using System.Net; +using System.Windows; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.Draw.Vfx.VfxNative; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data; +using Lumina.Excel.Sheets; +using Lumina.Excel.Sheets.Experimental; +using Newtonsoft.Json; +using System; +using System; +using System.Collections.Generic; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Linq; +using System.Net.Http; +using System.Net.Http; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Xml.Linq; +using static FFXIVClientStructs.FFXIV.Client.Game.ActionManager.Delegates; +using static FFXIVClientStructs.FFXIV.Client.UI.Misc.DataCenterHelper; +using static KodakkuAssist.Script.ScriptAccessory; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + + +namespace Veever.A_Realm_Reborn.Guildhests; + + +[ScriptType(name: Name, + territorys: [190, 191, 192, 214, 215, 216, 219, 220, 221, 222, 223, 298, 299, 300], + guid: "3797d75b-cf0b-4de8-ad06-71f343af697b", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateStr)] +public class Guildhests +{ + + const string NoteStr = + $""" + v{Version} + ----- 请在使用前阅读注意事项 以及根据情况修改用户设置 ----- + 1. 本脚本为全部行会令的合集,通过当前所在副本ID(CurrentTerritoryId)区分各行会令的机制。 + 2. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 3. 标点开关以及本地开关在用户设置里可自行选择 + 鸭门 + ---------------------------------- + ----- Please read the notes before use and adjust user settings as needed. ----- + 1. This single script covers every guildhest; each encounter's mechanics are gated by the current territory ID (CurrentTerritoryId). + 2. If you need a draw or notice any issues, @ me on DC or DM me. + 3. The toggle for marking and local marking can be adjusted in the user settings. + 4. TTS not support English, you can turn off TTS in user settings. + Duckmen. + """; + + const string UpdateStr = + $""" + v{Version} + + 鸭门 + ---------------------------------- + + Duckmen. + """; + + private const string Name = "行会令合集 [Guildhests]"; + private const string Version = "0.0.0.1"; + private const string DebugVersion = "0.0.1.0"; + + private const bool Debugging = true; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value == more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useAntiKnockBack { get; set; } = false; + + //[UserSetting("指路开关(Guide arrow toggle)")] + //public bool isLead { get; set; } = true; + + [UserSetting("目标标记开关(Target Marker toggle)")] + public bool isMark { get; set; } = true; + + [UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + public void DebugMsg(string str, ScriptAccessory sa) + { + if (!isDebug) return; + sa.Log.Debug($"[DEBUG] {str}"); + } + + private ScriptAccessory _sa = null; + private bool isDRTTS = false; + + private int attackCount_UTA; + private int NotifyCount_BTS; + private int attackCount_BTS; + private readonly object NotifyLock_BTS = new object(); + private int pollenClusterTTSCount_PPP; + private readonly object pollenClusterLock_PPP = new object(); + private int showTargetCount_SB; + private readonly object showTargetLock_SB = new object(); + private int showTargetCount_FSN; + private readonly object showTargetLock_FSN = new object(); + private int attackCount_ATV; + private int attackCount_MTF; + private int attackCount_SAC; + private int drawEyeDelay_SAC; + private int SleepCount_LLQ; + private readonly object SleepLock_LLQ = new object(); + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + _sa = sa; + + _ = ScriptVersionChecker.CheckVersionAsync( + sa, + "3797d75b-cf0b-4de8-ad06-71f343af697b", + Version, + showNotification: true + ); + + + sa.Method.ClearFrameworkUpdateAction(this); + + attackCount_UTA = 0; + NotifyCount_BTS = 0; + attackCount_BTS = 0; + pollenClusterTTSCount_PPP = 0; + showTargetCount_SB = 0; + showTargetCount_FSN = 0; + attackCount_ATV = 0; + attackCount_MTF = 0; + attackCount_SAC = 0; + drawEyeDelay_SAC = 0; + SleepCount_LLQ = 0; + + RefreshParams(); + } + + private void RefreshParams() + { + } + +#region 各行会令脚本 + +#region 190 讨伐彷徨死灵 Under the Armor +// v0.0.0.5: + +[ScriptMethod(name: "—————— LV.10 讨伐彷徨死灵 (190) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_UTA(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "指路", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:515"])] +public void Navi_UTA(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 190) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = @event.SourcePosition(); + dp.Scale = new(1); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); +} + +[ScriptMethod(name: "删除指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:870"])] +public void delNavi_UTA(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 190) return; + if (attackCount_UTA == 1) + { + accessory.Method.RemoveDraw(".*"); + } + attackCount_UTA++; +} + +[ScriptMethod(name: "钢铁正义", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:356"])] +public async void IronJustice_UTA(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 190) return; + // 下踢 + if (IbcHelper.GetPlayerRole(accessory, accessory.Data.MyObject) == "Tank") + { + accessory.Method.UseAction(accessory.Data.MyObject.TargetObject.EntityId, 7540); + } + + await Task.Delay(1000); + + // 扫腿 + if (IbcHelper.GetPlayerRole(accessory, accessory.Data.MyObject) == "Tank") + { + accessory.Method.UseAction(accessory.Data.MyObject.TargetObject.EntityId, 7863); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Iron Justice"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +#endregion 190 讨伐彷徨死灵 Under the Armor + +#region 214 完成集团战训练 Basic Training: Enemy Parties +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.10 完成集团战训练!(214) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_BTP(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "飞翼斩", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:1015"])] +public void WingCutter_BTP(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 214) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Wing Cutter"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(7); + dp.Radian = float.Pi / 180 * 60; + dp.DestoryAt = 2200; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "发霉喷嚏", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:579"])] +public void MoldySneeze_BTP(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 214) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Moldy Sneeze"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(6); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "副本指示", eventType: EventTypeEnum.Director, eventCondition: ["Instance:80032711"])] +public void Notify_BTP(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 214) return; + if (isDebug) accessory.Method.SendChat($"/e {@event.Command()}"); + if (@event.Command() == 00000000) + { + if (isText) accessory.Method.TextInfo("在紫圈内等待敌人出现", duration: 4700, true); + accessory.TTS("在紫圈内等待敌人出现", isTTS, isDRTTS); + } +} + +#endregion 214 完成集团战训练 Basic Training: Enemy Parties + +#region 215 突破所有关门,讨伐最深处的敌人 Basic Training: Enemy Strongholds +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.15 突破所有关门,讨伐最深处的敌人!(215) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_BTS(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "吃力跳跃", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:1006"])] +public void LaboredLeap_BTS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 215) return; + if (isText) accessory.Method.TextInfo("钢铁,远离Boss", duration: 4000, true); + accessory.TTS("钢铁,远离Boss", isTTS, isDRTTS); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "吃力跳跃"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(9.5f); + dp.DestoryAt = 3800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "操纵杆画图指示", eventType: EventTypeEnum.MorelogCompat, eventCondition: ["MorlogId:101"])] +public async void Notify_BTS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 215) return; + await Task.Delay(1000); + if (HelperExtensions.GetCurrentTerritoryId() != 215) return; + lock (NotifyLock_BTS) + { + if (NotifyCount_BTS == 0) + { + List vectorList = new List + { + new Vector3(-377.86f, 24.19f, -562.98f), + new Vector3(-392.50f, 24.94f, -487.56f) + }; + + for (int i = 0; i <= 1; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"操纵杆画图指示{i}"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Position = vectorList[i]; + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(0.8f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + NotifyCount_BTS++; + } +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:870"])] +public void delDraw_BTS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 215) return; + if (attackCount_BTS == 1) + { + accessory.Method.RemoveDraw(".*"); + } + attackCount_BTS++; +} + +#endregion 215 突破所有关门,讨伐最深处的敌人 Basic Training: Enemy Strongholds + +#region 216 捕获金币龟 Hero on the Half Shell +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.15 捕获金币龟!(216) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_HHS(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "龟足踏", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:417"])] +public void TortoiseStomp_HHS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 216) return; + if (isText) accessory.Method.TextInfo("钢铁,远离Boss", duration: 3800, true); + accessory.TTS("钢铁,远离Boss", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "龟足踏"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(11f); + dp.DestoryAt = 3800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "钢铁正义", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:448"])] +public void IronJustice_HHS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 216) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Stagnant Spray"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(8); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "火元精指路", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1130"])] +public void Navi_HHS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 216) return; + if (isText) accessory.Method.TextInfo("集中攻击火元精", duration: 4000, true); + accessory.TTS("集中攻击火元精", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = @event.SourceId(); + dp.Scale = new(1); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); +} + +[ScriptMethod(name: "删除绘制+成堆的香草指路", eventType: EventTypeEnum.Chat, eventCondition: ["Message:regex:^(用火元精核心在我这里点燃香草!| Use the core to light the herb patch lying before me. | ファイアスプライトの核で、\nアタイの目の前の香草に着火するんだ!)$"])] +public async void delDraw_HHS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 216) return; + accessory.Method.RemoveDraw(".*"); + await Task.Delay(50); + + if (isText) accessory.Method.TextInfo("点燃成堆的香草, 并将金币龟带进黄色催眠范围内", duration: 5000, true); + accessory.TTS("点燃成堆的香草, 并将金币龟带进黄色催眠范围内", isTTS, isDRTTS); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "成堆的香草指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = new Vector3(-167.92f, -29.31f, 84.42f); + dp.Scale = new(1); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); +} + +[ScriptMethod(name: "删除绘制+成堆的香草范围", eventType: EventTypeEnum.ObjectEffect, eventCondition: ["Id2:1"])] +public async void delDraw2_HHS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 216) return; + if (isDebug) accessory.Method.SendChat("/e Im in"); + accessory.Method.RemoveDraw(".*"); + await Task.Delay(50); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"成堆的香草范围"; + dp.Color = new Vector4(255 / 255.0f, 255 / 255.0f, 0 / 255.0f, 0.8f); + dp.Position = new Vector3(-167.92f, -29.31f, 84.42f); + dp.Scale = new Vector2(5f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:832"])] +public async void delDraw3_HHS(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 216) return; + accessory.Method.RemoveDraw(".*"); +} + +#endregion 216 捕获金币龟 Hero on the Half Shell + +#region 191 驱除剧毒妖花 Pulling Poison Posies +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.20 驱除剧毒妖花!(191) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_PPP(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "花粉块", eventType: EventTypeEnum.MorelogCompat, eventCondition: ["MorlogId:101"])] +public async void pollenCluster_PPP(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 191) return; + await Task.Delay(1000); + if (HelperExtensions.GetCurrentTerritoryId() != 191) return; + lock (pollenClusterLock_PPP) + { + if (pollenClusterTTSCount_PPP == 0) + { + if (isText) accessory.Method.TextInfo("不要踩在毒圈范围内, 后续紫圈同理,头铁可以开浴血类技能站在圈内猛猛输出", duration: 6000, true); + accessory.TTS("不要踩在毒圈范围内, 后续紫圈同理", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "花粉块"; + dp.Color = new Vector4(138 / 255.0f, 43 / 255.0f, 251 / 226.0f, 0.5f); + dp.Position = new Vector3(-154.83f, -0.63f, 171.10f); + dp.Scale = new Vector2(11f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + pollenClusterTTSCount_PPP++; + } +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.Director, eventCondition: ["Command:80000002", "Instance:00110002"])] +public async void delDraw_PPP(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 191) return; + accessory.Method.RemoveDraw(".*"); +} + +[ScriptMethod(name: "闪雷直击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:325"])] +public void Thunderstrike_PPP(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 191) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "闪雷直击"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(3, 11.2f); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); +} + +[ScriptMethod(name: "嚎叫", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:336"])] +public async void delDraw2_PPP(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 191) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"嚎叫"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(4.8f); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +#endregion 191 驱除剧毒妖花 Pulling Poison Posies + +#region 192 消灭恶徒团伙寄生蜂团 Stinging Back +// v0.0.0.3: 标点开关以及本地开关在用户设置里可自行选择 + +[ScriptMethod(name: "—————— LV.20 消灭恶徒团伙寄生蜂团!(192) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_SB(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "目标显示", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(595|145|148|519|146|144|147)$"])] +public async void showTarget_SB(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 192) return; + await Task.Delay(2000); + if (HelperExtensions.GetCurrentTerritoryId() != 192) return; + lock (showTargetLock_SB) + { + if (showTargetCount_SB == 0) + { + if (isText) accessory.Method.TextInfo("优先攻击红色医疗兵", duration: 5000, true); + accessory.TTS("优先攻击红色医疗兵", isTTS, isDRTTS); + } + if (@event.DataId() == 595) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"{@event.SourceId()}"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(0.5f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + else if (@event.DataId() == 144) + { + if (isText) accessory.Method.TextInfo("集中攻击红腹群点蜂兵", duration: 5000, true); + accessory.TTS("集中攻击红腹群点蜂兵", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"{@event.SourceId()}"; + dp.Color = new Vector4(255 / 255.0f, 255 / 255.0f, 0 / 226.0f, 1f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(0.5f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + else + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"{@event.SourceId()}"; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.Owner = @event.SourceId(); + dp1.ScaleMode = ScaleMode.ByTime; + dp1.Scale = new Vector2(0.5f); + dp1.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp1); + } + showTargetCount_SB++; + } +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:regex:^(595|145|148|519|146|144|147)$"])] +public async void delDraw_SB(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 192) return; + accessory.Method.RemoveDraw($"{@event.SourceId()}"); +} + +[ScriptMethod(name: "强突", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:866"])] +public void Heartstopper_SB(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 192) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "强突"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(3, 3.4f); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); +} + +[ScriptMethod(name: "删除绘制1", eventType: EventTypeEnum.Director, eventCondition: ["Command:00000001", "Instance:00180001"])] +public async void delDraw1_SB(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 192) return; + accessory.Method.RemoveDraw(".*"); +} + +#endregion 192 消灭恶徒团伙寄生蜂团 Stinging Back + +#region 220 讨伐梦幻之布拉奇希奥 All's Well that Ends in the Well +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.25 讨伐梦幻之布拉奇希奥!(220) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_ASW(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "恐怖视线", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:513"])] +public void DreadGaze_ASW(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 220) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "恐怖视线"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(7); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "石化吐息", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:505"])] +public void Petribreath_ASW(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 220) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "石化吐息"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(7.6f); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 2800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "notify", eventType: EventTypeEnum.Chat, eventCondition: ["Message:regex:^(发现梦幻之布拉奇希奥了!\n不要慌,按一直以来的战术就好!| Without an ally to draw his magicks away from the others, Briaxio will unleash his bane on all of you! | 夢幻のブラキシオを確認した!\n盾役はその務めを果たし、味方を守り抜け!)$"])] +public async void notify_ASW(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 220) return; + if (isText) accessory.Method.TextInfo("优先攻击 梦幻 布拉奇希奥", duration: 5000, true); + accessory.TTS("优先攻击 梦幻 布拉奇希奥", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"notify"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 1.0f); + dp.Owner = 0x400050A2; + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(0.5f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "麻痹", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:308"])] +public void Paralyze_ASW(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 220) return; + if (isText) accessory.Method.TextInfo("注意打断 梦幻 布拉奇希奥", duration: 3700, true); + accessory.TTS("注意打断 梦幻 布拉奇希奥", isTTS, isDRTTS); +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.Director, eventCondition: ["Command:80000022", "Instance:80032717"])] +public async void delDraw_ASW(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 220) return; + accessory.Method.RemoveDraw(".*"); +} + +#endregion 220 讨伐梦幻之布拉奇希奥 All's Well that Ends in the Well + +#region 219 击溃哥布林炸弹军团 Flicking Sticks and Taking Names +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.25 击溃哥布林炸弹军团!(219) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_FSN(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "目标显示", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(531|530|587|533)$"])] +public async void showTarget_FSN(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 219) return; + await Task.Delay(2000); + if (HelperExtensions.GetCurrentTerritoryId() != 219) return; + lock (showTargetLock_FSN) + { + if (@event.DataId() == 587) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"{@event.SourceId()}"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(0.5f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + else if (@event.DataId() == 533) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"{@event.SourceId()}"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(1f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + else + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"{@event.SourceId()}"; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.Owner = @event.SourceId(); + dp1.ScaleMode = ScaleMode.ByTime; + dp1.Scale = new Vector2(0.5f); + dp1.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp1); + } + showTargetCount_FSN++; + + } +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:regex:^(531|530|587)$"])] +public async void delDraw_FSN(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 219) return; + accessory.Method.RemoveDraw($"{@event.SourceId()}"); +} + +[ScriptMethod(name: "bomb", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:596"])] +public async void bomb_FSN(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 219) return; + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"{@event.SourceId()}"; + dp1.Color = accessory.Data.DefaultDangerColor; + dp1.Owner = @event.SourceId(); + dp1.ScaleMode = ScaleMode.ByTime; + dp1.Scale = new Vector2(3f); + dp1.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp1); +} + +[ScriptMethod(name: "删除绘制1", eventType: EventTypeEnum.Director, eventCondition: ["Command:40000002", "Instance:80032718"])] +public async void delDraw1_FSN(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 219) return; + accessory.Method.RemoveDraw(".*"); +} + +#endregion 219 击溃哥布林炸弹军团 Flicking Sticks and Taking Names + +#region 222 讨伐坑道中出现的妖异 Annoy the Void +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.30 讨伐坑道中出现的妖异!(222) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_ATV(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "定罪", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:912"])] +public void Condemnation_ATV(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 222) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Condemnation"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(7.3f); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "notify", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:870", "SourceId:40011DA3"])] +public void notify_ATV(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 222) return; + if (isDebug) accessory.Method.SendChat($"/e notifycount: {attackCount_ATV}"); + if (attackCount_ATV == 1) + { + if (isText) accessory.Method.TextInfo("集中攻击 布索, 注意交互蓝色火焰削弱BOSS", duration: 6000, true); + accessory.TTS("集中攻击 布索, 注意交互蓝色火焰削弱BOSS", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + } + attackCount_ATV++; +} + +[ScriptMethod(name: "哀叫", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:602"])] +public async void OvertoneShriek_ATV(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 222) return; + await Task.Delay(50); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"哀叫范围"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(4.7f); + dp.DestoryAt = 2800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +#endregion 222 讨伐坑道中出现的妖异 Annoy the Void + +#region 221 讨伐污染源头魔界花 More than a Feeler +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.30 讨伐污染源头魔界花!(221) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_MTF(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "指路", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1126"])] +public async void Navi_MTF(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 221) return; + if (isDebug) accessory.Method.SendChat("/e im in Navi"); + await Task.Delay(1500); + if (HelperExtensions.GetCurrentTerritoryId() != 221) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = @event.SourceId(); + dp.Scale = new(1); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); +} + +[ScriptMethod(name: "删除指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:872"])] +public void delNavi_MTF(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 221) return; + if (isDebug) accessory.Method.SendChat("/e im in delNavi"); + if (attackCount_MTF == 1) + { + accessory.Method.RemoveDraw("指路"); + if (isText) accessory.Method.TextInfo("集中攻击 剧毒魔花谭琳", duration: 6000, true); + accessory.TTS("集中攻击 剧毒魔花谭琳", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + } + attackCount_MTF++; +} + +[ScriptMethod(name: "臭气", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:604"])] +public void BadBreath_MTF(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 221) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "BadBreath"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(15); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "腐坏泡泡", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1027"])] +public void stalebubble_MTF(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 221) return; + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "stalebubble"; + dp1.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 225.0f, 1f); + dp1.Owner = @event.SourceId(); + dp1.ScaleMode = ScaleMode.ByTime; + dp1.Scale = new Vector2(0.5f); + dp1.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp1); +} + +[ScriptMethod(name: "腐坏泡泡销毁", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:1027"])] +public void Delstalebubble_MTF(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 221) return; + accessory.Method.RemoveDraw($"{@event.SourceId()}"); +} + +#endregion 221 讨伐污染源头魔界花 More than a Feeler + +#region 223 注意无敌的眷属,讨伐大型妖异 Shadow and Claw +// v0.0.0.4: + +[ScriptMethod(name: "—————— LV.35 注意无敌的眷属,讨伐大型妖异!(223) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_SAC(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "定罪", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:912"])] +public void Condemnation_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Condemnation"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(7.3f); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "notify", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:870", "SourceId:400111A2"])] +public void notify_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + if (isDebug) accessory.Method.SendChat($"/e notifycount: {attackCount_SAC}"); + if (attackCount_SAC == 0) + { + if (isText) accessory.Method.TextInfo("集中攻击 暗影魔爪", duration: 2000, true); + accessory.TTS("集中攻击 暗影魔爪", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + } + attackCount_SAC++; +} + +[ScriptMethod(name: "暗影之眼范围", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1802"])] +public async void drawEye_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + if (isText) accessory.Method.TextInfo("不要攻击 暗影之眼, 注意远离钢铁", duration: 5000, true); + accessory.TTS("不要攻击 暗影之眼, 注意远离钢铁", isTTS, isDRTTS); + + for (int i = 0; i < 5; i++) + { + switch (i) + { + case 0: + drawEyeDelay_SAC = 7900; + break; + case 1: + drawEyeDelay_SAC = 18100; + break; + case 2: + drawEyeDelay_SAC = 28400; + break; + case 3: + drawEyeDelay_SAC = 38700; + break; + case 4: + drawEyeDelay_SAC = 49000; + break; + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"暗影之眼范围{i}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(14f); + dp.Delay = drawEyeDelay_SAC; + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"暗影之眼范围{i}描边"; + dp1.Scale = new(14f); + dp1.InnerScale = new(13.9f); + dp1.Radian = float.Pi * 2; + dp1.Color = new Vector4(178 / 255.0f, 34 / 255.0f, 34 / 255.0f, 10.0f); + dp1.Owner = @event.SourceId(); + dp1.Delay = drawEyeDelay_SAC; + dp1.DestoryAt = 2500; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp1); + } + if (!LocalMark) await Task.Delay(1000); // 拟人 + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Stop1, LocalMark); +} + +[ScriptMethod(name: "三连爪", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:932"])] +public void Triclip_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "三连爪"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(4f, 5f); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); +} + +[ScriptMethod(name: "咒言", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(585|586)$"])] +public async void Curse_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + await Task.Delay(5); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"咒言范围"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(3.8f); + dp.DestoryAt = 2400; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.Director, eventCondition: ["Command:40000002", "Instance:8003271B"])] +public async void delDraw_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + accessory.Method.RemoveDraw(".*"); +} + +[ScriptMethod(name: "骇人嚎叫", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:933"])] +public async void FrightfulRoar_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"骇人嚎叫"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(8f); + dp.DestoryAt = 2700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "删除骇人嚎叫", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:933"])] +public async void delFrightfulRoar_SAC(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 223) return; + accessory.Method.RemoveDraw("骇人嚎叫"); +} + +#endregion 223 注意无敌的眷属,讨伐大型妖异 Shadow and Claw + +#region 298 讨伐爆弹怪的女王 Long Live the Queen +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.35 讨伐爆弹怪的女王!(298) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_LLQ(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "自爆", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:337"])] +public async void SelfDestruct_LLQ(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 298) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"自爆"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(6.5f); + dp.DestoryAt = 2900; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "催眠", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:145"])] +public async void Sleep_LLQ(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 298) return; + lock (SleepLock_LLQ) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"催眠"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(2f); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + switch (SleepCount_LLQ) + { + case 0: + if (isText) accessory.Method.TextInfo("催眠 注意打断", duration: 2000, true); + accessory.TTS("催眠 注意打断", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Bind1, LocalMark); + break; + case 1: + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Bind2, LocalMark); + break; + case 2: + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Bind3, LocalMark); + break; + } + SleepCount_LLQ++; + } +} + +[ScriptMethod(name: "notify", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1805"])] +public void notify_LLQ(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 298) return; + if (isText) accessory.Method.TextInfo("集中攻击 爆弹女王", duration: 5000, true); + accessory.TTS("集中攻击 爆弹女王", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); +} + +[ScriptMethod(name: "大爆炸", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:1007"])] +public async void bigExplosion_LLQ(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 298) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"大爆炸"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(8.3f); + dp.DestoryAt = 5100; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo("钢铁, 注意远离", duration: 5000, true); + accessory.TTS("钢铁, 注意远离", isTTS, isDRTTS); +} + +#endregion 298 讨伐爆弹怪的女王 Long Live the Queen + +#region 299 歼灭特殊阵型的妖异 Ward Up +// v0.0.0.4: + +[ScriptMethod(name: "—————— LV.40 歼灭特殊阵型的妖异!(299) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_WU(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "无敌提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:325"])] +public void GOD_WU(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 299) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"无敌提示"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(1.5f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo("攻击非无敌的小怪", duration: 5000, true); + accessory.TTS("攻击非无敌的小怪", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.TargetId(), KodakkuAssist.Module.GameOperate.MarkType.Stop1, LocalMark); +} + +[ScriptMethod(name: "无敌提示删除", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:325"])] +public void delGod_WU(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 299) return; + accessory.Method.RemoveDraw("无敌提示"); + accessory.Method.MarkClear(); +} + +[ScriptMethod(name: "恐怖眼", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:740"])] +public void bigExplosion_WU(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 299) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"恐怖眼"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(6f); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +#endregion 299 歼灭特殊阵型的妖异 Ward Up + +#region 300 制止三方混战的巨人族,守住遗物 Solemn Trinity +// v0.0.0.3: + +[ScriptMethod(name: "—————— LV.40 制止三方混战的巨人族,守住遗物!(300) ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] +public void Divider_ST(Event @event, ScriptAccessory accessory) { } + +[ScriptMethod(name: "超压斧", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:720"])] +public void Overpower_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Overpower"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(7.9f); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "Boss指示0", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1816"])] +public async void BossNotify_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Boss指示0"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(2f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo("攻击 长房克利俄斯", duration: 5000, true); + accessory.TTS("攻击 长房克利俄斯", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); +} + +[ScriptMethod(name: "Boss指示删除0", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:1816"])] +public async void delBossNotify_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + accessory.Method.RemoveDraw("Boss指示0"); + accessory.Method.MarkClear(); +} + +[ScriptMethod(name: "Boss指示1", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1824"])] +public async void BossNotify1_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Boss指示1"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(2f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo("优先攻击 被标记Boss", duration: 5000, true); + accessory.TTS("优先攻击 被标记Boss", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); +} + +[ScriptMethod(name: "Boss指示2", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:1819"])] +public async void BossNotify2_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Boss指示2"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(2f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack2, LocalMark); +} + +[ScriptMethod(name: "野蛮咆哮", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:596"])] +public async void BarbarousScream_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"野蛮咆哮"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(5.3f); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "巨大抨击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:692"])] +public void ColossalSlam_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "巨大抨击"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(9f); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); +} + +[ScriptMethod(name: "骨灰", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:600"])] +public async void BonePowder_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"骨灰"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(3f); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); +} + +[ScriptMethod(name: "删除绘制", eventType: EventTypeEnum.Director, eventCondition: ["Command:40000002", "Instance:8003271E"])] +public async void delDraw_ST(Event @event, ScriptAccessory accessory) +{ + if (HelperExtensions.GetCurrentTerritoryId() != 300) return; + accessory.Method.RemoveDraw(".*"); +} + +#endregion 300 制止三方混战的巨人族,守住遗物 Solemn Trinity + +#endregion 各行会令脚本 + +} + + + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } + + 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 string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 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 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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战物理) / "Ranged Physical DPS"(远程物理) / "Ranged Magical DPS"(远程魔法) / "Unknown" / "None" + /// 大版本更新需要维护 + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + + return playerObject.ClassJob.RowId switch + { + // Tank: 骑士、战士、暗黑骑士、绝枪战士 + 19 or 21 or 32 or 37 => "Tank", + // Healer: 白魔法师、学者、占星术士、贤者 + 24 or 28 or 33 or 40 => "Healer", + // Melee DPS: 武僧、龙骑士、忍者、武士、钐镰客、蝰蛇剑士 + 20 or 22 or 30 or 34 or 39 or 41 => "Melee DPS", + // Ranged Physical DPS: 吟游诗人、机工士、舞者 + 23 or 31 or 38 => "Ranged Physical DPS", + // Ranged Magical DPS: 黑魔法师、召唤师、赤魔法师、青魔法师、绘灵法师 + 25 or 27 or 35 or 36 or 42 => "Ranged Magical DPS", + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + public static string GetObjectName(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return "None"; + return obj.Name.ToString(); + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } + + [Flags] + public enum DrawState : uint + { + Invisibility = 0x00_00_00_02, + IsLoading = 0x00_00_08_00, + SomeNpcFlag = 0x00_00_01_00, + MaybeCulled = 0x00_00_04_00, + MaybeHiddenMinion = 0x00_00_80_00, + MaybeHiddenSummon = 0x00_80_00_00, + } + + public static unsafe DrawState* ActorDrawState(IGameObject actor) + => (DrawState*)(&((GameObject*)actor.Address)->RenderFlags); + + /// + /// 检查对象可见性(Read) + /// + /// ScriptAccessory + /// Obj need check + /// true=检查可见,false=检查不可见 + /// 如果符合检查条件返回 True + public static unsafe bool IsActorVisible(this ScriptAccessory sa, IGameObject? obj, bool checkVisible = true) + { + if (obj == null) return false; + + try + { + var state = *ActorDrawState(obj); + bool isVisible = (state & DrawState.Invisibility) == 0; + + return checkVisible ? isVisible : !isVisible; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } + + /// + /// 设置对象可见性(Write) + /// + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + if (actor == null) return; + + try + { + var statePtr = ActorDrawState(actor); + if (visible) + *statePtr &= ~DrawState.Invisibility; + else + *statePtr |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + throw; + } + } + + /// + /// Check DrawState + /// + public static unsafe bool HasDrawState(this ScriptAccessory sa, IGameObject? actor, DrawState state) + { + if (actor == null) return false; + + try + { + return (*ActorDrawState(actor) & state) != 0; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float? rotation = null, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosTarget(ScriptAccessory accessory, Vector3 pos, Vector3 targetpos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.TargetPosition = targetpos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTargetPos(ScriptAccessory accessory, ulong owner, Vector3 targetPos, Vector2 scale, int duration, string name, + Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObjectPos(ScriptAccessory accessory, ulong obj, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = obj; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class Extensions +{ + public static void antiKnockBack(this MethodAccessory method, ScriptAccessory sa) + { + // 7559 沉稳咏唱 + // 7548 亲疏自行 + var myobj = sa.Data.MyObject; + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Tank" || IbcHelper.GetPlayerRole(sa, myobj) == "Melee DPS" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Physical DPS") + { + method.UseAction(myobj.EntityId, 7548); + } + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Healer" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Magical DPS") + { + method.UseAction(myobj.EntityId, 7559); + } + } + public static float hitboxRadius(this MethodAccessory method, IPlayerCharacter obj) + { + return obj.HitboxRadius; + } + + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + accessory.Method.TTS(text); + } +} + +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 + +#region 脚本版本检查 +public static class ScriptVersionChecker +{ + private const string OnlineRepoUrl = "https://raw.githubusercontent.com/VeeverSW/Kodakku-Script/refs/heads/main/OnlineRepo.json"; + private static readonly HttpClient _httpClient = new HttpClient(); + + /// + /// 在线仓库脚本信息 + /// + public class OnlineScriptInfo + { + public string Name { get; set; } = ""; + public string Guid { get; set; } = ""; + public string Version { get; set; } = ""; + public string Author { get; set; } = ""; + public string Repo { get; set; } = ""; + public string DownloadUrl { get; set; } = ""; + public string Note { get; set; } = ""; + public string UpdateInfo { get; set; } = ""; + public int[] TerritoryIds { get; set; } = Array.Empty(); + } + + /// + /// 版本 + /// + public enum VersionCompareResult + { + /// 当前版本较新或相同 + UpToDate, + /// 有新版本可用 + UpdateAvailable, + /// 未找到匹配的脚本 + NotFound, + /// 检查失败 + Error + } + + /// + /// 检查脚本版本 + /// + /// ScriptAccessory + /// 脚本GUID + /// 当前版本号 + /// 是否显示通知 + /// 版本比较结果 + public static async Task<(VersionCompareResult result, OnlineScriptInfo? onlineInfo)> CheckVersionAsync( + ScriptAccessory sa, + string guid, + string currentVersion, + bool showNotification = true) + { + try + { + sa.Log.Debug($"开始检查脚本版本 (GUID: {guid}, 当前版本: {currentVersion})"); + + var response = await _httpClient.GetStringAsync(OnlineRepoUrl); + var onlineScripts = JsonConvert.DeserializeObject>(response); + + if (onlineScripts == null || onlineScripts.Count == 0) + { + sa.Log.Error("无法解析在线仓库数据"); + return (VersionCompareResult.Error, null); + } + + var onlineScript = onlineScripts.FirstOrDefault(s => + s.Guid.Equals(guid, StringComparison.OrdinalIgnoreCase)); + + if (onlineScript == null) + { + //sa.Log.Debug($"在线仓库中未找到 GUID 为 {guid} 的脚本"); + //if (showNotification) + //{ + // sa.Method.TextInfo("该脚本未在在线仓库中注册", 3000); + //} + return (VersionCompareResult.NotFound, null); + } + + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 找到在线脚本: {onlineScript.Name}, 在线版本: {onlineScript.Version}"); + + var compareResult = CompareVersions(currentVersion, onlineScript.Version); + + if (compareResult < 0) + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新 (当前: {currentVersion})"); + if (showNotification) + { + sa.Method.TextInfo( + $"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新\n当前版本: {currentVersion}", + 5000, + true); + } + return (VersionCompareResult.UpdateAvailable, onlineScript); + } + else + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 当前版本已是最新 (当前: {currentVersion}, 在线: {onlineScript.Version})"); + + return (VersionCompareResult.UpToDate, onlineScript); + } + } + catch (HttpRequestException ex) + { + //sa.Log.Error($"网络请求失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败: 网络错误", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + catch (Exception ex) + { + //sa.Log.Error($"版本检查失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + } + + /// + /// 比较版本号 + /// + /// 版本1 (例如: "0.0.0.3") + /// 版本2 (例如: "0.0.0.5") + /// 负数: version1 < version2, 0: 相等, 正数: version1 > version2 + private static int CompareVersions(string version1, string version2) + { + var v1Parts = version1.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + var v2Parts = version2.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + + int maxLength = Math.Max(v1Parts.Length, v2Parts.Length); + + for (int i = 0; i < maxLength; i++) + { + int v1Part = i < v1Parts.Length ? v1Parts[i] : 0; + int v2Part = i < v2Parts.Length ? v2Parts[i] : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } +} +#endregion + +public static class HelperExtensions +{ + public static unsafe uint GetCurrentTerritoryId() + { + return AgentMap.Instance()->CurrentTerritoryId; + } +} diff --git a/VeeverSW/02-A-Realm-Reborn/Haukke-Manor.cs b/VeeverSW/02-A-Realm-Reborn/Haukke-Manor.cs new file mode 100644 index 0000000..8999da5 --- /dev/null +++ b/VeeverSW/02-A-Realm-Reborn/Haukke-Manor.cs @@ -0,0 +1,791 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using FFXIVClientStructs; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using FFXIVClientStructs.FFXIV.Client.UI; +using System.Runtime.Intrinsics.Arm; +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +namespace Veever.A_Realm_Reborn.Haukke_Manor; + +[ScriptType(name: "LV.28 名门府邸静语庄园", territorys: [1040], guid: "964f1a0a-5b2a-4473-b41b-a170bc823f67", + version: "0.0.0.5", author: "Veever", note: noteStr)] + +public class Haukke_Manor +{ + const string noteStr = + """ + v0.0.0.5: + 1. 绿色标记为需要捡的钥匙,红色代表不捡 + 2. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 3. 如果想要鲶鱼精标记请确保你打开了ACT并且安装了鲶鱼精插件 + 鸭门。 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = true; + + [UserSetting("指路开关")] + public bool isLead { get; set; } = true; + + [UserSetting("目标标记开关")] + public bool isMark { get; set; } = true; + + [UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("是否进行场地标点引导")] + public bool PostNamazuPrint { get; set; } = true; + + [UserSetting("鲶鱼精邮差端口设置")] + public int PostNamazuPort { get; set; } = 2019; + + [UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点)")] + public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + + //private readonly object OminousWindMarkerTTSLock = new object(); + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + if (PostNamazuPrint) PostWaymark(accessory); + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] + public void debug(Event @event, ScriptAccessory accessory) + { + + } + + #region Waymark + private static readonly Vector3 posA = new Vector3(48.76f, 0.00f, 36.00f); + private static readonly Vector3 posB = new Vector3(16.94f, 0.00f, 70.82f); + private static readonly Vector3 posC = new Vector3(-36.62f, 0.00f, 16.15f); + + public void PostWaymark(ScriptAccessory accessory) + { + var waymark = new NamazuHelper.Waymark(accessory); + waymark.AddWaymarkType("A", posA); + waymark.AddWaymarkType("B", posB); + waymark.AddWaymarkType("C", posC); + + waymark.SetJsonPayload(LocalMark, PostNamazuisLocal); + waymark.PostWaymarkCommand(PostNamazuPort); + } + + private static readonly Vector3 newposA = new Vector3(-46.52f, -0.00f, 0.01f); + private static readonly Vector3 newposB = new Vector3(-31.85f, -18.80f, -0.03f); + private static readonly Vector3 targetC = new Vector3(-31.85f, -18.80f, -24f); + private static readonly Vector3 newposC = new Vector3(-2.16f, -18.80f, 40.35f); + + private static readonly Vector3 newposD = new Vector3(-16.24f, -15.69f, 27.75f); + + public void PostWaymark1(ScriptAccessory accessory) + { + var waymark1 = new NamazuHelper.Waymark(accessory); + waymark1.AddWaymarkType("A", newposA); + waymark1.AddWaymarkType("B", newposB); + waymark1.AddWaymarkType("C", newposC); + waymark1.AddWaymarkType("D", newposD); + + waymark1.SetJsonPayload(LocalMark, PostNamazuisLocal); + waymark1.PostWaymarkCommand(PostNamazuPort); + } + + + private static readonly Vector3 newposAA = new Vector3(49.11f, 8.37f, 0.00f); + private static readonly Vector3 newposBB = new Vector3(25.25f, 17.00f, 0.02f); + + public void PostWaymark2(ScriptAccessory accessory) + { + var waymark1 = new NamazuHelper.Waymark(accessory); + waymark1.AddWaymarkType("A", newposAA); + waymark1.AddWaymarkType("B", newposBB); + + waymark1.SetJsonPayload(LocalMark, PostNamazuisLocal); + waymark1.PostWaymarkCommand(PostNamazuPort); + } + + [ScriptMethod(name: "钥匙mark", eventType: EventTypeEnum.ObjectChanged, eventCondition: [])] + public async void keymark(Event @event, ScriptAccessory accessory) + { + await Task.Delay(1000); + + var keyId0 = 2000302; + if (@event.DataId() == keyId0) + { + DebugMsg("key0", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultSafeColor, scaleByTime: false); + } + else if (@event.DataId() == keyId0 + 1) + { + DebugMsg("key1", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultSafeColor, scaleByTime: false); + } + else if (@event.DataId() == keyId0 + 2) + { + DebugMsg("key2", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultDangerColor, scaleByTime: false); + } else if (@event.DataId() == 2000324) + { + DebugMsg("key2000324", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultSafeColor, scaleByTime: false); + Vector3 pos = new Vector3(-46.49f, 0.00f, 0.05f); + DrawHelper.DrawDisplacement(accessory, pos, new Vector2(1.5f), 999999999, $"displacement-{@event.DataId()}", accessory.Data.DefaultSafeColor); + PostWaymark1(accessory); + DrawHelper.DrawArrow(accessory, newposB, targetC, 1f, 10f, 999999999, "arrow", accessory.Data.DefaultSafeColor); + + } else if (@event.DataId() == 2000305) + { + DebugMsg("key2000305", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultDangerColor, scaleByTime: false); + } else if (@event.DataId() == 2000325) + { + DebugMsg("key2000325", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultSafeColor, scaleByTime: false); + } else if (@event.DataId() == 2001235) + { + DebugMsg("key2001235", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultSafeColor, scaleByTime: false); + } else if (@event.DataId() == 2000327) + { + DebugMsg("key2000327", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultDangerColor, scaleByTime: false); + + } + + if (@event.Operate() == "Remove") + { + accessory.Method.RemoveDraw($"标记-{@event.DataId()}"); + if (@event.DataId() == 2000324) + { + await Task.Delay(5000); + accessory.Method.RemoveDraw($"displacement-{@event.DataId()}"); + } + } + + } + + [ScriptMethod(name: "钥匙mark初始", eventType: EventTypeEnum.ObjectChanged, eventCondition: [])] + public async void keymarkInit(Event @event, ScriptAccessory accessory) + { + await Task.Delay(6000); + + var keyId0 = 2000302; + if (@event.DataId() == keyId0) + { + DebugMsg("key0", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultSafeColor, scaleByTime: false); + } + else if (@event.DataId() == keyId0 + 1) + { + DebugMsg("key1", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultSafeColor, scaleByTime: false); + } + else if (@event.DataId() == keyId0 + 2) + { + DebugMsg("key2", accessory); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(0.7f), 999999999, $"标记-{@event.DataId()}", accessory.Data.DefaultDangerColor, scaleByTime: false); + } + + if (@event.Operate() == "Remove") + { + accessory.Method.RemoveDraw($"标记-{@event.DataId()}"); + } + + } + + #endregion + + + + + + #region 小怪 + [ScriptMethod(name: "暗黑雾", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29776"])] + public void DarkMist(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("注意远离或打断", duration: 3700, true); + if (isTTS) accessory.Method.TTS("注意远离或打断"); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + DrawHelper.DrawCircleObject(accessory, @event.SourceId(), new Vector2(8f), 3700, $"DarkMist-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "DarkMist Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:29776"], userControl: false)] + public void DarkMistClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"DarkMist-{@event.SourceId()}"); + } + + + [ScriptMethod(name: "恐怖视线", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:513"])] + public void DreadGaze(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawFanOwner(accessory, @event.SourceId(), 0, new Vector2(7.35f), 90, 2700, $"DarkMist-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "DarkMist Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:513"], userControl: false)] + public void DreadGazeClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"DreadGaze-{@event.SourceId()}"); + } + + #endregion + + + #region Boss1 + [ScriptMethod(name: "虚空烈炎", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:855"])] + public void VoidFireII(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("注意远离或打断", duration: 3700, true); + if (isTTS) accessory.Method.TTS("注意远离或打断"); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(5f), 2700, $"VoidFireII-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "VoidFireII Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:855"], userControl: false)] + public void VoidFireIIClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"VoidFireII-{@event.SourceId()}"); + } + + [ScriptMethod(name: "暗黑雾", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:705"])] + public void DarkMist1(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("注意远离", duration: 3700, true); + if (isTTS) accessory.Method.TTS("注意远离"); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(9.4f), 3700, $"DarkMist1-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "DarkMist1 Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:705"], userControl: false)] + public void DarkMist1Clear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"DarkMist1-{@event.SourceId()}"); + } + + + + + #endregion + + + + #region Boss2 + [ScriptMethod(name: "冰棘屏障", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:859"])] + public async void IceSpikes(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("打断庄园的小丑", duration: 3000, true); + if (isTTS) accessory.Method.TTS("打断庄园的小丑"); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Bind1, LocalMark); + await Task.Delay(4000); + accessory.Method.MarkClear(); + } + + [ScriptMethod(name: "吸魂", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:860"])] + public async void SoulDrain(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("眩晕庄园的总管", duration: 3700, true); + if (isTTS) accessory.Method.TTS("眩晕庄园的总管"); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Bind1, LocalMark); + await Task.Delay(4000); + accessory.Method.MarkClear(); + } + #endregion + + + #region Boss3 + [ScriptMethod(name: "石化眼", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28648"])] + public void PetrifyingEye(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + if (isText) accessory.Method.TextInfo("背对眼睛", duration: 4700, true); + if (isTTS) accessory.Method.TTS("背对眼睛"); + } + + [ScriptMethod(name: "女仆标记", eventType: EventTypeEnum.Targetable, eventCondition: ["DataId:14506", "Targetable:True"])] + public void TargetMark(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("优先攻击随从女仆", duration: 4700, true); + if (isTTS) accessory.Method.TTS("优先攻击随从女仆"); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + + } + + [ScriptMethod(name: "Boss3暗黑雾", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28646"])] + public void Boss3DarkMist(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("注意远离或下踢打断", duration: 3700, true); + if (isTTS) accessory.Method.TTS("注意远离或下踢打断"); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(9f), 3700, $"DarkMist-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Boss3DarkMist Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:28646"], userControl: false)] + public void Boss3DarkMistClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"DarkMist-{@event.SourceId()}"); + } + + + [ScriptMethod(name: "Draw Clear", eventType: EventTypeEnum.Death, eventCondition: ["TargetDataId:14504"], userControl: false)] + public void DrawClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + #endregion + + + + + #region Helpers + + public unsafe static float GetStatusRemainingTime(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); + } + } + + + private unsafe uint[] ScanTether(Event evt, ScriptAccessory sa, uint id) + { + if (sa?.Data?.Objects == null) return Array.Empty(); + List dataId = [id]; + List players = []; + foreach (var fire in sa.Data.Objects.Where(x => dataId.Contains(x.DataId))) + { + if (fire?.Address == null) continue; + var targetId = ((BattleChara*)fire.Address)->Vfx.Tethers[0].TargetId.ObjectId; + players.Add(targetId); + } + DebugMsg($"players: {string.Join(", ", players)}", sa); + return players.ToArray(); + } + + public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanOwner(ScriptAccessory accessory, ulong owner, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle* (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } +} + +} + + +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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + +public static class IbcHelper +{ + public static KodakkuAssist.Data.IGameObject? GetById(ScriptAccessory accessory, uint id) + { + return accessory.Data.Objects.SearchByEntityId(id); + } + + public static KodakkuAssist.Data.IGameObject? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me); + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } + + public static IEnumerable GetParty(ScriptAccessory accessory) + { + foreach (var pid in accessory.Data.PartyList) + { + var obj = accessory.Data.Objects.SearchByEntityId(pid); + if (obj != null) yield return obj; + } + } + + public static IEnumerable GetPartyEntities(ScriptAccessory accessory) + { + return accessory.Data.Objects.Where(obj => accessory.Data.PartyList.Contains(obj.EntityId)); + } + + public static bool HasStatus(this IBattleChara ibc, uint statusId) + { + return ibc.StatusList.Any(x => x.StatusId == statusId); + } + + public static bool HasStatusAny(this IBattleChara ibc, uint[] statusIds) + { + return ibc.StatusList.Any(x => statusIds.Contains(x.StatusId)); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } +} + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } + #endregion +} diff --git a/VeeverSW/02-A-Realm-Reborn/Snowcloak.cs b/VeeverSW/02-A-Realm-Reborn/Snowcloak.cs new file mode 100644 index 0000000..81126bb --- /dev/null +++ b/VeeverSW/02-A-Realm-Reborn/Snowcloak.cs @@ -0,0 +1,2134 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Common.Lua; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Net.Http; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Xml.Linq; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.A_Realm_Reborn.Snowcloak; + +[ScriptType(name: Name, territorys: [1062], guid: "e64621a3-5ff4-40e2-9070-88e69191a1c0", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateStr)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class Snowcloak +{ + const string NoteStr = + $""" + v{Version} + ----- 请在使用前阅读注意事项 以及根据情况修改用户设置 ----- + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 鸭门 + ---------------------------------- + ----- Please read the notes before use and adjust user settings as needed. ----- + 1. If you need a draw or notice any issues, @ me on DC or DM me. + Duckmen. + """; + + const string UpdateStr = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.50 凛冽洞天披雪大冰壁 [Snowcloak]"; + private const string Version = "0.0.0.3"; + private const string DebugVersion = "a"; + + private const bool Debugging = false; + + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useAntiKnockBack { get; set; } = false; + + //[UserSetting("指路开关(Guide arrow toggle)")] + //public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + + private readonly object CountLock = new object(); + private bool isEdgeTTS = false; + + public void DebugMsg(string str, ScriptAccessory sa) + { + if (!isDebug) return; + sa.Log.Debug($"[DEBUG] {str}"); + } + + private ScriptAccessory _sa = null; + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + _sa = sa; + + _ = ScriptVersionChecker.CheckVersionAsync( + sa, + "e64621a3-5ff4-40e2-9070-88e69191a1c0", + Version, + showNotification: true + ); + + + sa.Method.ClearFrameworkUpdateAction(this); + + RefreshParams(); + } + + private void RefreshParams() + { + + } + + #region Mobs + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void destoryCancelAction(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($".*{ev.TargetId}"); + } + + [ScriptMethod(name: "崩落 - Pillar Impact", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:3160"])] + public void PillarImpact(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(5.9f), 2700, $"Pillar Impact-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Pillar Impact Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:3160"], userControl: false)] + public void PillarImpactClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Pillar Impact-{ev.SourceId}"); + } + + [ScriptMethod(name: "飞散 - Shatter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:3200"])] + public void Shatter(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 2700, $"Shatter-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Shatter Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:3200"], userControl: false)] + public void ShatterClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Shatter-{ev.SourceId}"); + } + + [ScriptMethod(name: "患病喷嚏 - Sickly Sneeze", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31264"])] + public void SicklySneeze(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(7.9f), 90, 2200, $"Sickly Sneeze-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Sickly Sneeze Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:31264"], userControl: false)] + public void SicklySneezeClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Sickly Sneeze-{ev.SourceId}"); + } + + [ScriptMethod(name: "强击 - Heavy Attack", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:3098"])] + public void HeavyAttack(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(9.8f), 120, 2200, $"Heavy Attack-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Heavy Attack Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:3098"], userControl: false)] + public void HeavyAttackClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Heavy Attack-{ev.SourceId}"); + } + + [ScriptMethod(name: "双重碎击 - Double Smash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:3259"])] + public void DoubleSmash(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(7.6f), 120, 2200, $"Double Smash-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Double Smash Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:3259"], userControl: false)] + public void DoubleSmashClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Double Smash-{ev.SourceId}"); + } + + [ScriptMethod(name: "飞翼斩 - Wing Cutter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:331"])] + public void WingCutter(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(6.9f), 60, 2200, $"Wing Cutter-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Wing Cutter Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:331"], userControl: false)] + public void WingCutterClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Wing Cutter-{ev.SourceId}"); + } + #endregion + + + #region Boss1 + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "风雪 - Snow Drift", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:3080"])] + public void Boss1SnowDrift(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if(isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + + + [ScriptMethod(name: "寒潮 - Cold Wave", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:3111"])] + public void Boss1ColdWave(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 3700, $"Cold Wave-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + #endregion + + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "殴打 - Buffet", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29585"])] + public void Boss2Buffet(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(12f), 120, 2700, $"Buffet-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Buffet Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:29585"], userControl: false)] + public void Boss2BuffetClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Buffet-{ev.SourceId}"); + } + + [ScriptMethod(name: "狂风暴雪 - Northerlies", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29582"])] + public void Boss2Northerlies(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + + [ScriptMethod(name: "大雪球 - Heavy Snow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29589"])] + public void Boss2HeavySnow(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(15f), 6700, $"Heavy Snow-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "小雪球 - Light Snow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29590"])] + public void Boss2LightSnow(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(2f), 6700, $"Light Snow-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "回转 - Spin", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29586"])] + public void Boss2Spin(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(11f), 4700, $"Spin-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "冻环 - Frozen Circle", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29591"])] + public void Boss2FrozenCircle(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(10f), 3700, $"Frozen Circle-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false, delay: 1000); + } + + [ScriptMethod(name: "冻环(分散) - Frozen Spike(Spread)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29592"])] + public void Boss2FrozenSpike_Ch(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 4700, $"Frozen Circle-{ev.SourceId}", color: new Vector4(0, 1, 1, ColorAlpha), scaleByTime: false); + } + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "月下咆哮 - Lunar Cry", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29599"])] + public void Boss3LunarCry(Event ev, ScriptAccessory sa) + { + var iceList = IbcHelper.GetByDataId(sa, 14714); + + foreach(var ice in iceList) + { + if (ice == null || !ice.IsValid()) continue; + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Lunar Cry-{ice.EntityId}"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = ice.EntityId; + dp.TargetObject = ev.SourceId; + dp.Rotation = -float.Pi; + dp.Scale = new Vector2(80); + dp.Radian = 30 * (float.Pi / 180); + dp.DestoryAt = 7700; + dp.FixRotation = false; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + string msg = language == Language.Chinese ? "躲在完整的冰柱后面随后准备远离" : "Get behind a full ice pillar, then move out"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5000, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + + [ScriptMethod(name: "Pillar Remove", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29648"], userControl:false)] + public void Boss3PillarRemove(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Lunar Cry-{ev.SourceId}"); + } + + [ScriptMethod(name: "飞散 - Pillar Shatter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29601"])] + public void Boss3PillarShatter(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(8f), 1700, $"Pillar Shatter-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "死刑 - Tankbuster", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29596"])] + public void Boss3Tankbuster(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"坦克死刑" : $"Tank Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4300, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + + [ScriptMethod(name: "苍穹咆哮 - Heavensward Roar", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29593"])] + public void HeavenswardRoar(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(50f), 60, 4700, $"Heavensward Roar-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "千年风雪 - Thousand-year Storm", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:29595"])] + public void Boss3Thousand_yearStorm(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + #endregion +} + + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战) / "Ranged DPS"(远程) / "Unknown" / "None" + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + return playerObject.ClassJob.Value.Role switch + { + 1 => "Tank", // 坦克 + 4 => "Healer", // 治疗 + 2 => "Melee DPS", // 近战DPS + 3 => "Ranged DPS", // 远程DPS + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + public static string GetObjectName(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return "None"; + return obj.Name.ToString(); + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } + + [Flags] + public enum DrawState : uint + { + Invisibility = 0x00_00_00_02, + IsLoading = 0x00_00_08_00, + SomeNpcFlag = 0x00_00_01_00, + MaybeCulled = 0x00_00_04_00, + MaybeHiddenMinion = 0x00_00_80_00, + MaybeHiddenSummon = 0x00_80_00_00, + } + + public static unsafe DrawState* ActorDrawState(IGameObject actor) + => (DrawState*)(&((GameObject*)actor.Address)->RenderFlags); + + /// + /// 检查对象可见性(Read) + /// + /// ScriptAccessory + /// Obj need check + /// true=检查可见,false=检查不可见 + /// 如果符合检查条件返回 True + public static unsafe bool IsActorVisible(this ScriptAccessory sa, IGameObject? obj, bool checkVisible = true) + { + if (obj == null) return false; + + try + { + var state = *ActorDrawState(obj); + bool isVisible = (state & DrawState.Invisibility) == 0; + + return checkVisible ? isVisible : !isVisible; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } + + /// + /// 设置对象可见性(Write) + /// + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + if (actor == null) return; + + try + { + var statePtr = ActorDrawState(actor); + if (visible) + *statePtr &= ~DrawState.Invisibility; + else + *statePtr |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + throw; + } + } + + /// + /// Check DrawState + /// + public static unsafe bool HasDrawState(this ScriptAccessory sa, IGameObject? actor, DrawState state) + { + if (actor == null) return false; + + try + { + return (*ActorDrawState(actor) & state) != 0; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float? rotation = null, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosTarget(ScriptAccessory accessory, Vector3 pos, Vector3 targetpos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.TargetPosition = targetpos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTargetPos(ScriptAccessory accessory, ulong owner, Vector3 targetPos, Vector2 scale, int duration, string name, + Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObjectPos(ScriptAccessory accessory, ulong obj, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = obj; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isEdgeTTS) + { + accessory.Method.TTS(text); + } +} + +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 + +#region 脚本版本检查 +public static class ScriptVersionChecker +{ + private const string OnlineRepoUrl = "https://raw.githubusercontent.com/VeeverSW/Kodakku-Script/refs/heads/main/OnlineRepo.json"; + private static readonly HttpClient _httpClient = new HttpClient(); + + /// + /// 在线仓库脚本信息 + /// + public class OnlineScriptInfo + { + public string Name { get; set; } = ""; + public string Guid { get; set; } = ""; + public string Version { get; set; } = ""; + public string Author { get; set; } = ""; + public string Repo { get; set; } = ""; + public string DownloadUrl { get; set; } = ""; + public string Note { get; set; } = ""; + public string UpdateInfo { get; set; } = ""; + public int[] TerritoryIds { get; set; } = Array.Empty(); + } + + /// + /// 版本 + /// + public enum VersionCompareResult + { + /// 当前版本较新或相同 + UpToDate, + /// 有新版本可用 + UpdateAvailable, + /// 未找到匹配的脚本 + NotFound, + /// 检查失败 + Error + } + + /// + /// 检查脚本版本 + /// + /// ScriptAccessory + /// 脚本GUID + /// 当前版本号 + /// 是否显示通知 + /// 版本比较结果 + public static async Task<(VersionCompareResult result, OnlineScriptInfo? onlineInfo)> CheckVersionAsync( + ScriptAccessory sa, + string guid, + string currentVersion, + bool showNotification = true) + { + try + { + sa.Log.Debug($"开始检查脚本版本 (GUID: {guid}, 当前版本: {currentVersion})"); + + var response = await _httpClient.GetStringAsync(OnlineRepoUrl); + var onlineScripts = JsonConvert.DeserializeObject>(response); + + if (onlineScripts == null || onlineScripts.Count == 0) + { + sa.Log.Error("无法解析在线仓库数据"); + return (VersionCompareResult.Error, null); + } + + var onlineScript = onlineScripts.FirstOrDefault(s => + s.Guid.Equals(guid, StringComparison.OrdinalIgnoreCase)); + + if (onlineScript == null) + { + //sa.Log.Debug($"在线仓库中未找到 GUID 为 {guid} 的脚本"); + //if (showNotification) + //{ + // sa.Method.TextInfo("该脚本未在在线仓库中注册", 3000); + //} + return (VersionCompareResult.NotFound, null); + } + + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 找到在线脚本: {onlineScript.Name}, 在线版本: {onlineScript.Version}"); + + var compareResult = CompareVersions(currentVersion, onlineScript.Version); + + if (compareResult < 0) + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新 (当前: {currentVersion})"); + if (showNotification) + { + sa.Method.TextInfo( + $"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新\n当前版本: {currentVersion}", + 5000, + true); + } + return (VersionCompareResult.UpdateAvailable, onlineScript); + } + else + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 当前版本已是最新 (当前: {currentVersion}, 在线: {onlineScript.Version})"); + + return (VersionCompareResult.UpToDate, onlineScript); + } + } + catch (HttpRequestException ex) + { + //sa.Log.Error($"网络请求失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败: 网络错误", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + catch (Exception ex) + { + //sa.Log.Error($"版本检查失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + } + + /// + /// 比较版本号 + /// + /// 版本1 (例如: "0.0.0.3") + /// 版本2 (例如: "0.0.0.5") + /// 负数: version1 < version2, 0: 相等, 正数: version1 > version2 + private static int CompareVersions(string version1, string version2) + { + var v1Parts = version1.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + var v2Parts = version2.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + + int maxLength = Math.Max(v1Parts.Length, v2Parts.Length); + + for (int i = 0; i < maxLength; i++) + { + int v1Part = i < v1Parts.Length ? v1Parts[i] : 0; + int v2Part = i < v2Parts.Length ? v2Parts[i] : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } +} +#endregion diff --git a/VeeverSW/03-Heavensward/A11N.cs b/VeeverSW/03-Heavensward/A11N.cs new file mode 100644 index 0000000..447094b --- /dev/null +++ b/VeeverSW/03-Heavensward/A11N.cs @@ -0,0 +1,197 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.Draw; +using Dalamud.Utility.Numerics; +using System.Numerics; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Intrinsics.Arm; + +namespace Meva.Heavensward.KodakkuAssist.Alexander11; + +[ScriptType(name: "LV.60 亚历山大机神城 天动之章3", territorys: [582], guid: "c9446d8e-118d-ec98-fcbc-732213b0a265", version: "0.0.0.6", author: "Meva & Veever", note:noteStr)] +public class A11N +{ + const string noteStr = + """ + v0.0.0.6: + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + #region 小怪 + [ScriptMethod(name: "汽油弹钢铁", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6850"])] + public void 汽油弹钢铁(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("钢铁", duration: 2000, true); + if (isTTS) accessory.Method.TTS("钢铁"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "汽油弹钢铁区域"; + dp.Owner = @event.SourceId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new Vector2(8.5f); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "汽油弹月环", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6851"])] + public void 汽油弹月环(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("月环", duration: 2000, true); + if (isTTS) accessory.Method.TTS("月环"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "汽油弹月环"; + dp.Owner = @event.SourceId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new Vector2(12); + dp.InnerScale = new Vector2(3); + dp.Radian = float.Pi * 2; + dp.DestoryAt = 5000; + dp.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + #endregion + + #region Boss + [ScriptMethod(name: "百式聚能炮", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6745"])] + public void 百式聚能炮(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("核爆点名", duration: 4000, true); + if (isTTS) accessory.Method.TTS("核爆点名"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "百式聚能炮"; + dp.Owner = @event.TargetId(); + } + + [ScriptMethod(name: "黑暗命运", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6681"])] + public void 黑暗命运(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("流血AOE", duration: 4000, true); + if (isTTS) accessory.Method.TTS("流血AOE"); + } + + // 摧毁者冲击(直线AOE,Action 6751/6787) + [ScriptMethod(name: "摧毁者冲击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(6751|6787)$"])] + public void 摧毁者冲击(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离直线点名", duration: 2000, true); + if (isTTS) accessory.Method.TTS("远离直线点名"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "摧毁者冲击"; + dp.Scale = new(6,50); + dp.TargetObject = @event.TargetId(); + dp.Owner = @event.SourceId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + // 螺旋桨强风(需要找掩体) + [ScriptMethod(name: "螺旋桨强风", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(6744|6773)$"])] + public void 螺旋桨强风(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("躲在装置后", duration: 3000); + if (isTTS) accessory.Method.TTS("躲在装置后"); + } + + // 等离子护盾提示(NPC 3647) + [ScriptMethod(name: "护盾提示", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataID:6101"])] + public void 护盾提示(Event @event, ScriptAccessory accessory) + { + accessory.Method.TextInfo("从护盾正面击杀护盾", duration: 4000); + if (isTTS) accessory.Method.TTS("从护盾正面击杀护盾"); + } + + #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 string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + public static float SourceRotation(this Event @event) + { + return float.TryParse(@event["SourceRotation"], out var rot) ? rot : 0; + } + + 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 JsonConvert.DeserializeObject(@event["SourceName"]) ?? string.Empty; + } + + 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"]); + } +} + diff --git a/VeeverSW/03-Heavensward/A12N.cs b/VeeverSW/03-Heavensward/A12N.cs new file mode 100644 index 0000000..f0a42e4 --- /dev/null +++ b/VeeverSW/03-Heavensward/A12N.cs @@ -0,0 +1,207 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.Draw; +using Dalamud.Utility.Numerics; +using System.Numerics; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace Meva.Heavensward.KodakkuAssist.Alexander12; + +[ScriptType(name: "LV.60 亚历山大机神城 天动之章4", territorys: [583], guid: "c77e794f-f3bc-37fc-810b-bb153ba399c1", version: "0.0.0.6", author: "Meva & Veever", note:noteStr)] +public class A12N +{ + const string noteStr = + """ + v0.0.0.6: + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "百万神圣", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6887"])] + public void 百万神圣(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 2000, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + // 十字圣礼1(十字激光1) + [ScriptMethod(name: "十字圣礼1", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6885"])] + public void 十字圣礼1(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("躲避十字激光", duration: 2000, true); + if (isTTS) accessory.Method.TTS("躲避十字激光"); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "十字圣礼1"; + dp.Owner = @event.SourceId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new(16.0f, 60.0f); // X=半径16,Y=长度60 + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + + // 十字圣礼2(十字激光2,旋转90度) + [ScriptMethod(name: "十字圣礼2", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6885"])] + public void 十字圣礼2(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "十字圣礼2"; + dp.Owner = @event.SourceId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new(16.0f, 60.0f); + dp.Rotation = float.Pi / 2; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + + // 重力异常(扩大黑圈) + [ScriptMethod(name: "重力异常", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6891"])] + public void 重力异常(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离扩大黑圈", duration: 2000, true); + if (isTTS) accessory.Method.TTS("远离扩大黑圈"); + } + + // 白光之鞭(点名AOE,Buff 562触发) + [ScriptMethod(name: "白光之鞭", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:562"])] + public void 白光之鞭(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("分散", duration: 2000, true); + if (isTTS) accessory.Method.TTS("分散"); + + var circleDp = accessory.Data.GetDefaultDrawProperties(); + circleDp.Name = "白光之鞭区域"; + circleDp.Owner = @event.TargetId(); + circleDp.Color = accessory.Data.DefaultDangerColor; + circleDp.Scale = new(4); + circleDp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, circleDp); + } + + // 集团罪(分摊,Buff 1122触发) + [ScriptMethod(name: "集团罪", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:1122"])] + public void 集团罪(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + if (isText) accessory.Method.TextInfo($"靠近{tname}分摊", 2000); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "分摊区域"; + dp.Owner = @event.TargetId(); + dp.Color = accessory.Data.DefaultSafeColor; + dp.Scale = new(4); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + // 加重罪(大圈,Buff 1121触发) + [ScriptMethod(name: "加重罪", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:1121"])] + public void 加重罪(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("分散", 2000); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "加重罪区域"; + dp.Owner = @event.TargetId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new(2.5f); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "圣餐", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:6908"])] + public void 圣餐(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("放圈连线点名", duration: 4000, true); + if (isTTS) accessory.Method.TTS("放圈连线点名"); + } +} + + +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 string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + public static float SourceRotation(this Event @event) + { + return float.TryParse(@event["SourceRotation"], out var rot) ? rot : 0; + } + + 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 JsonConvert.DeserializeObject(@event["SourceName"]) ?? string.Empty; + } + + 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"]); + } +} + + diff --git a/VeeverSW/03-Heavensward/A9N.cs b/VeeverSW/03-Heavensward/A9N.cs new file mode 100644 index 0000000..192b821 --- /dev/null +++ b/VeeverSW/03-Heavensward/A9N.cs @@ -0,0 +1,131 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.Draw; +using Dalamud.Utility.Numerics; +using System.Numerics; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace Meva.Heavensward.KodakkuAssist.Alexander9; + +[ScriptType(name: "LV.60 亚历山大机神城 天动之章1", territorys: [580], guid: "7cb80e0d-e693-6ca9-c46e-c96b0ec5d109", version: "0.0.0.6", author: "Meva & Veever", note: noteStr)] +public class A9N +{ + const string noteStr = + """ + v0.0.0.6 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + // 废料爆发提示(躲在石头后) + [ScriptMethod(name: "废料爆发", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6926"])] + public void 废料爆发(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("躲在石头后!", duration: 2000); + if (isTTS) accessory.Method.TTS("躲在石头后"); + } + + + // 击杀小怪提示(拉进发光地板) + [ScriptMethod(name: "击杀小怪提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["DataId:6922"])] + public void 击杀小怪提示(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("拉进发光地板击杀", duration: 2000); + } + + // 击杀大怪提醒(对角击杀) + [ScriptMethod(name: "大怪击杀提示", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:6354"])] + public void 大怪击杀提示(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("在发光地板对角击杀", duration: 4000); + if (isTTS) accessory.Method.TTS("在发光地板对角击杀"); + } +} + + +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 string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + public static float SourceRotation(this Event @event) + { + return float.TryParse(@event["SourceRotation"], out var rot) ? rot : 0; + } + + 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 JsonConvert.DeserializeObject(@event["SourceName"]) ?? string.Empty; + } + + 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"]); + } +} + diff --git a/VeeverSW/03-Heavensward/the-Aetherochemical-Research-Facility.cs b/VeeverSW/03-Heavensward/the-Aetherochemical-Research-Facility.cs new file mode 100644 index 0000000..f82edf8 --- /dev/null +++ b/VeeverSW/03-Heavensward/the-Aetherochemical-Research-Facility.cs @@ -0,0 +1,734 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; + +namespace Veever.Heavensward.AetherochemicalResearchFacility; + +[ScriptType(name: "LV.60 血战苍穹魔科学研究所", territorys: [1110], guid: "dd08165c-b709-4100-a96e-65f2c7ae4f3b", + version: "0.0.0.8", author: "Veever", note: noteStr)] + +public class AetherochemicalResearchFacility +{ + public int fireCount; + public int iceCount; + public int Away0061Count; + public int TetherCount; + + private readonly object fireLock = new object(); + private readonly object iceLock = new object(); + private readonly object tetherLock = new object(); + const string noteStr = + """ + v0.0.0.8: + 1. 现在支持文字横幅/TTS开关 + 2. 以前的这几个脚本的底层扩展目前懒得重构(就能加啥随便加了) + 鸭门。 + """; + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + fireCount = 0; + iceCount = 0; + Away0061Count = 0; + TetherCount = 0; + } + + #region Boss1 + [ScriptMethod(name: "Boss1魔导炮塔", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(373[67])$"])] + public void Boss1MagitekTurret(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("集火魔导炮塔", duration: 10000, true); + if (isTTS) accessory.Method.TTS("集火魔导炮塔"); + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "魔导炮塔Pos"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = @event.SourcePosition(); + dp.Scale = new Vector2(2); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Boss1魔导激光", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:4321"])] + public void Boss1MagitekRay(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("躲避激光", duration: 2700, true); + if (isTTS) accessory.Method.TTS("躲避激光"); + } + + [ScriptMethod(name: "Boss1魔导扩散弹", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:4316"])] + public void Boss1MagitekSpread(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("去boss身后", duration: 4200, true); + if (isTTS) accessory.Method.TTS("去boss身后"); + } + #endregion + + + + + #region Boss2 + [ScriptMethod(name: "Boss2顺劈播报", eventType: EventTypeEnum.Chat, eventCondition: ["Message:启动合成生物性能评测系统——赫鲁玛奇斯。"])] + public void Boss2Notification(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("此Boss大多数技能均为顺劈,非坦克职业不要站在Boss正面", duration: 5000, true); + if (isTTS) accessory.Method.TTS("此Boss大多数技能均为顺劈,非坦克职业不要站在Boss正面"); + } + + [ScriptMethod(name: "Boss2石化", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:4331"])] + public void Boss2Petrifaction(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("背对Boss", duration: 2700, true); + if (isTTS) accessory.Method.TTS("背对Boss"); + } + + + [ScriptMethod(name: "Boss2弹道导弹", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:4771"])] + public void Boss2BallisticMissile(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("双人分摊,不要超过两个人", duration: 4000, true); + if (isTTS) accessory.Method.TTS("双人分摊,不要超过两个人"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "弹道导弹"; + dp.Color = new Vector4(1.0f, 0.4f, 1.0f, 1.0f); + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(4); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + + [ScriptMethod(name: "Boss2分摊", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:005D"])] + public void Boss2Stack(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Boss2分摊"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 5500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"与{tname}分摊"); + } + #endregion + + + #region Boss3-P1 + [ScriptMethod(name: "Boss3AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31885"])] + public void Boss3AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "Boss3烈火魔力球", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:15782"])] + public void Boss3Fireball(Event @event, ScriptAccessory accessory) + { + lock (fireLock) + { + if (fireCount <= 4) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"{fireCount}烈火魔力球"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.SourcePosition(); + dp.Scale = new Vector2(8); + dp.DestoryAt = 6200; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + //accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + } + fireCount++; + } + } + + + [ScriptMethod(name: "Boss3黑夜波", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:32790"])] + public void Boss3GripofNight(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("去boss身后", duration: 5700, true); + if (isTTS) accessory.Method.TTS("去boss身后"); + } + + [ScriptMethod(name: "Boss3寒冰魔力球", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:15781"])] + public void Boss3Iceball(Event @event, ScriptAccessory accessory) + { + lock (iceLock) + { + if (iceCount <= 3) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"{iceCount}寒冰魔力球"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Position = @event.SourcePosition(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + //accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + if (iceCount == 1) + { + if (isText) accessory.Method.TextInfo("进入绿色安全区", duration: 5700, true); + if (isTTS) accessory.Method.TTS("进入绿色安全区"); + } + } + iceCount++; + } + } + + + [ScriptMethod(name: "Boss3分摊", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31892"])] + public void Boss3EndofDays(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + if (isText) accessory.Method.TextInfo($"与 {tname} 分摊", duration: 4700, true); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "EndofDays"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId(); + dp.TargetObject = @event.TargetId(); + dp.Scale = new Vector2(6, 25); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + if (isTTS) accessory.Method.TTS($"与 {tname} 分摊"); + } + + #endregion + + + #region Boss3-P2 + [ScriptMethod(name: "Boss3-P2清理变量", eventType: EventTypeEnum.Chat, eventCondition: ["Message:regex:^暗之力在涌动……\\r?\\n如火炎般热烈,如冰霜般寂静!$"])] + public void Boss3P2Clean(Event @event, ScriptAccessory accessory) + { + iceCount = 10; + fireCount = 10; + TetherCount = 10; + //accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + } + + + [ScriptMethod(name: "Boss3AOE2", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(319[10]0)$"])] + public void Boss3AOE2(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "Boss3大AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33024"])] + public void Boss3Annihilation(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("大AOE", duration: 6000, true); + if (isTTS) accessory.Method.TTS("大AOE"); + } + + + [ScriptMethod(name: "Boss3死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31911"])] + public void Boss3Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4700, true); + if (isTTS) accessory.Method.TTS("死刑准备"); + } + + [ScriptMethod(name: "Boss3分摊2", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00A1"])] + public void Boss3Stack(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Boss3分摊"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 5500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 5000, true); + if (isTTS) accessory.Method.TTS($"与{tname}分摊"); + } + + [ScriptMethod(name: "Boss3拉线", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3505"])] + public async void Boss3Away3505(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + await Task.Delay(36000 - 2000); + if (isText) accessory.Method.TextInfo("集合,等连线判定后拉线", duration: 1500, true); + if (isTTS) accessory.Method.TTS("集合,等连线判定后拉线"); + + await Task.Delay(2500); + if (isText) accessory.Method.TextInfo("拉开连线", duration: 4000, true); + if (isTTS) accessory.Method.TTS("拉开连线"); + } + } + + + [ScriptMethod(name: "Boss3分摊2-2", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31907"])] + public void Boss3EntropicFlame(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + if (isText) accessory.Method.TextInfo($"与 {tname} 分摊", duration: 4700, true); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "EntropicFlame"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId(); + dp.TargetObject = @event.TargetId(); + dp.Scale = new Vector2(6, 20); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + if (isTTS) accessory.Method.TTS($"与 {tname} 分摊"); + } + + [ScriptMethod(name: "Boss3集火立体魔法阵提醒", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:15788"])] + public void Boss3ArcaneSphere(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("集中攻击立体魔法阵", duration: 4700, true); + if (isTTS) accessory.Method.TTS("集中攻击立体魔法阵"); + } + + + #region Tether Basic Logic + //[ScriptMethod(name: "Boss3连线处理", eventType: EventTypeEnum.Tether, eventCondition: ["Id:006E"])] + //public async void Boss3Tether(Event @event, ScriptAccessory accessory) + //{ + // bool case1 = false, case2 = false; + + // lock (tetherLock) + // { + // if (TetherCount <= 1) + // { + // var pos1 = @event.SourcePosition(); + // var pos2 = @event.TargetPosition(); + // Vector3 midPoint = new Vector3(); + + // if (pos1.X == pos2.X) + // { + // if (pos1.Y == pos2.Y) + // { + // midPoint = new Vector3(pos1.X, pos1.Y, (pos1.Z + pos2.Z) / 2.0f); + // } else + // { + // midPoint = new Vector3(pos1.X, (pos1.Y + pos2.Y) / 2.0f, pos1.Z); + // } + // } else + // { + // midPoint = new Vector3((pos1.X + pos2.X) / 2.0f, pos1.Y, pos1.Z); + // } + + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"1烈火魔力球连线1(双大钢铁){TetherCount}"; + // dp.Color = accessory.Data.DefaultDangerColor; + // dp.Position = midPoint; + // dp.Scale = new Vector2(16); + // dp.DestoryAt = 10800; + // dp.ScaleMode = ScaleMode.ByTime; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + // accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + // } + // if (TetherCount == 2) + // { + // var pos1 = @event.SourcePosition(); + // var pos2 = @event.TargetPosition(); + // Vector3 midPoint = new Vector3(); + // if (pos1.X == pos2.X) + // { + // if (pos1.Y == pos2.Y) + // { + // midPoint = new Vector3(pos1.X, pos1.Y, (pos1.Z + pos2.Z) / 2.0f); + // } + // else + // { + // midPoint = new Vector3(pos1.X, (pos1.Y + pos2.Y) / 2.0f, pos1.Z); + // } + // } + // else + // { + // midPoint = new Vector3((pos1.X + pos2.X) / 2.0f, pos1.Y, pos1.Z); + // } + + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"2寒冰魔力球连线1(单月环){TetherCount}"; + // dp.Color = accessory.Data.DefaultSafeColor; + // dp.Position = midPoint; + // dp.Scale = new Vector2(5); + // dp.DestoryAt = 10000; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + // accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {qqfireCount}, TetherCount: {TetherCount}"); + + // var dp2 = accessory.Data.GetDefaultDrawProperties(); + // dp2.Name = $"{iceCount}寒冰魔力球连线1指路"; + // dp2.Owner = accessory.Data.Me; + // dp2.Color = accessory.Data.DefaultSafeColor; + // dp2.ScaleMode |= ScaleMode.YByDistance; + // dp2.TargetPosition = midPoint; + // dp2.Scale = new(2); + // dp2.DestoryAt = 6000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + // } + // if (TetherCount > 2 && TetherCount <= 4) + // { + // var pos1 = @event.SourcePosition(); + // var pos2 = @event.TargetPosition(); + // Vector3 midPoint = new Vector3(); + // if (pos1.X == pos2.X) + // { + // if (pos1.Y == pos2.Y) + // { + // midPoint = new Vector3(pos1.X, pos1.Y, (pos1.Z + pos2.Z) / 2.0f); + // } + // else + // { + // midPoint = new Vector3(pos1.X, (pos1.Y + pos2.Y) / 2.0f, pos1.Z); + // } + // } + // else + // { + // midPoint = new Vector3((pos1.X + pos2.X) / 2.0f, pos1.Y, pos1.Z); + // } + + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"3烈火魔力球连线2(双大钢铁){TetherCount}"; + // dp.Color = accessory.Data.DefaultDangerColor; + // dp.Position = midPoint; + // dp.Scale = new Vector2(16); + // dp.DestoryAt = 10000; + // dp.ScaleMode = ScaleMode.ByTime; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + // accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + // } + + // if (TetherCount == 10 || TetherCount == 13) + // { + // var spos = @event.SourcePosition(); + // var tpos = @event.TargetPosition(); + // float spostotal = spos.X + spos.Y + spos.Z; + // float tpostotal = tpos.X + tpos.Y + tpos.Z; + // float difference = Math.Abs(spostotal - tpostotal); + // int roundedDifference = (int)Math.Round(difference); + + // switch (roundedDifference) + // { + // case 14: + // case1 = true; + // break; + + // case 26: + // case2 = true; + // break; + + // default: + + // break; + // } + // } + + // TetherCount++; + // } + + // if (case1) + // { + // var pos1 = new Vector3(219.00f, -456.46f, 79.00f); + // var pos2 = new Vector3(241.00f, -456.46f, 79.00f); + // Vector3[] posStr = { pos1, pos2 }; + + // for (var i = 0; i <= 1; i++) + // { + // var dp1 = accessory.Data.GetDefaultDrawProperties(); + // dp1.Name = $"4烈火魔力球连线P2-1(双大钢铁){TetherCount}"; + // dp1.Color = accessory.Data.DefaultDangerColor; + // dp1.Position = posStr[i]; + // dp1.Scale = new Vector2(16); + // dp1.DestoryAt = 10000; + // dp1.ScaleMode = ScaleMode.ByTime; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + // accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + // } + // var pos3 = new Vector3(230f, -456.46f, 79.00f); + + // var dp2 = accessory.Data.GetDefaultDrawProperties(); + // dp2.Name = $"5寒冰魔力球连线P2-1(单月环){TetherCount}"; + // dp2.Color = accessory.Data.DefaultSafeColor; + // dp2.Position = pos3; + // dp2.Scale = new Vector2(5); + // dp2.Delay = 10000; + // dp2.DestoryAt = 6000; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + // accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + + // var dp3 = accessory.Data.GetDefaultDrawProperties(); + // dp3.Name = $"{iceCount}寒冰魔力球连线P2-1指路"; + // dp3.Owner = accessory.Data.Me; + // dp3.Color = accessory.Data.DefaultSafeColor; + // dp3.ScaleMode |= ScaleMode.YByDistance; + // dp3.TargetPosition = pos3; + // dp3.Scale = new(2); + // dp2.Delay = 10000; + // dp3.DestoryAt = 5000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + + // } + + // if (case2) + // { + // var pos1 = new Vector3(219.00f, -456.46f, 79.00f); + // var pos2 = new Vector3(241.00f, -456.46f, 79.00f); + // Vector3[] posStr = { pos1, pos2 }; + // var pos3 = new Vector3(230f, -456.46f, 79.00f); + + // var dp2 = accessory.Data.GetDefaultDrawProperties(); + // dp2.Name = $"6寒冰魔力球连线P2-2(单月环){TetherCount}"; + // dp2.Color = accessory.Data.DefaultSafeColor; + // dp2.Position = pos3; + // dp2.Scale = new Vector2(5); + // dp2.DestoryAt = 10000; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + // accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + + // var dp3 = accessory.Data.GetDefaultDrawProperties(); + // dp3.Name = $"{iceCount}寒冰魔力球连线P2-2指路"; + // dp3.Owner = accessory.Data.Me; + // dp3.Color = accessory.Data.DefaultSafeColor; + // dp3.ScaleMode |= ScaleMode.YByDistance; + // dp3.TargetPosition = pos3; + // dp3.Scale = new(2); + // dp3.DestoryAt = 10000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + + + // for (var i = 0; i <= 1; i++) + // { + // var dp1 = accessory.Data.GetDefaultDrawProperties(); + // dp1.Name = $"7烈火魔力球连线P2-2(双大钢铁){TetherCount}"; + // dp1.Color = accessory.Data.DefaultDangerColor; + // dp1.Position = posStr[i]; + // dp1.Scale = new Vector2(16); + // dp1.Delay = 10000; + // dp1.DestoryAt = 8000; + // dp1.ScaleMode = ScaleMode.ByTime; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + // accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + // } + // } + //} + #endregion + + + [ScriptMethod(name: "Boss3连线处理", eventType: EventTypeEnum.Tether, eventCondition: ["Id:006E"])] + public void Boss3Tether(Event @event, ScriptAccessory accessory) + { + bool case1 = false, case2 = false; + + lock (tetherLock) + { + var pos1 = @event.SourcePosition(); + var pos2 = @event.TargetPosition(); + var midPoint = CalculateMidPoint(pos1, pos2); + + if (TetherCount <= 1) + { + DrawCircle(accessory, midPoint, 16, 10800, $"1烈火魔力球连线1(双大钢铁){TetherCount}", accessory.Data.DefaultDangerColor, false); + } + else if (TetherCount == 2) + { + DrawCircle(accessory, midPoint, 5, 10000, $"2寒冰魔力球连线1(单月环){TetherCount}", accessory.Data.DefaultSafeColor, false); + DrawDisplacement(accessory, midPoint, 2, 6000, $"{iceCount}寒冰魔力球连线1指路", delay: 0); + } + else if (TetherCount > 2 && TetherCount <= 4) + { + DrawCircle(accessory, midPoint, 16, 10000, $"3烈火魔力球连线2(双大钢铁){TetherCount}", accessory.Data.DefaultDangerColor, false); + } + else if (TetherCount == 10 || TetherCount == 13) + { + float spostotal = pos1.X + pos1.Y + pos1.Z; + float tpostotal = pos2.X + pos2.Y + pos2.Z; + float difference = Math.Abs(spostotal - tpostotal); + int roundedDifference = (int)Math.Round(difference); + + switch (roundedDifference) + { + case 14: + case1 = true; + break; + case 26: + case2 = true; + break; + } + } + + TetherCount++; + } + + if (case1) + { + DrawCase1(accessory); + } + + if (case2) + { + DrawCase2(accessory); + } + } + + private Vector3 CalculateMidPoint(Vector3 pos1, Vector3 pos2) + { + if (pos1.X == pos2.X) + { + return pos1.Y == pos2.Y + ? new Vector3(pos1.X, pos1.Y, (pos1.Z + pos2.Z) / 2.0f) + : new Vector3(pos1.X, (pos1.Y + pos2.Y) / 2.0f, pos1.Z); + } + return new Vector3((pos1.X + pos2.X) / 2.0f, pos1.Y, pos1.Z); + } + + private void DrawCircle(ScriptAccessory accessory, Vector3 position, float scale, int duration, string name, Vector4 color, bool isDebug = false, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color; + dp.Position = position; + dp.Scale = new Vector2(scale); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + isDebug = false; + if (isDebug) + { + accessory.Method.SendChat($"iceCount:{iceCount}, fireCount: {fireCount}, TetherCount: {TetherCount}"); + } + } + + private void DrawDisplacement(ScriptAccessory accessory, Vector3 target, float scale, int duration, string name, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = new Vector2(scale); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private void DrawCase1(ScriptAccessory accessory) + { + var pos1 = new Vector3(219.00f, -456.46f, 79.00f); + var pos2 = new Vector3(241.00f, -456.46f, 79.00f); + var pos3 = new Vector3(230f, -456.46f, 79.00f); + Vector3[] positions = { pos1, pos2 }; + + foreach (var position in positions) + { + DrawCircle(accessory, position, 16, 10000, $"4烈火魔力球连线P2-1(双大钢铁){TetherCount}", accessory.Data.DefaultDangerColor, false); + } + + DrawCircle(accessory, pos3, 5, 5000, $"5寒冰魔力球连线P2-1(单月环){TetherCount}", accessory.Data.DefaultSafeColor, delay: 10000); + DrawDisplacement(accessory, pos3, 2, 5000, $"{iceCount}寒冰魔力球连线P2-1指路", delay: 10000); + } + + private void DrawCase2(ScriptAccessory accessory) + { + var pos1 = new Vector3(219.00f, -456.46f, 79.00f); + var pos2 = new Vector3(241.00f, -456.46f, 79.00f); + var pos3 = new Vector3(230f, -456.46f, 79.00f); + Vector3[] positions = { pos1, pos2 }; + + DrawCircle(accessory, pos3, 5, 9500, $"6寒冰魔力球连线P2-2(单月环){TetherCount}", accessory.Data.DefaultSafeColor); + DrawDisplacement(accessory, pos3, 2, 9000, $"{iceCount}寒冰魔力球连线P2-2指路"); + + foreach (var position in positions) + { + DrawCircle(accessory, position, 16, 2500, $"7烈火魔力球连线P2-2(双大钢铁){TetherCount}", accessory.Data.DefaultDangerColor, false, delay: 10000); + } + } + + + #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 string SourceName(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceName"]) ?? string.Empty; + } + + 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"]); + } +} + diff --git a/VeeverSW/03-Heavensward/the-Great-Gubal-Library-Hard.cs b/VeeverSW/03-Heavensward/the-Great-Gubal-Library-Hard.cs new file mode 100644 index 0000000..bb1aa71 --- /dev/null +++ b/VeeverSW/03-Heavensward/the-Great-Gubal-Library-Hard.cs @@ -0,0 +1,1795 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Veever.Heavensward.the_Great_Gubal_Library_Hard; + +[ScriptType(name: Name, territorys: [578], guid: "d376d381-8bd0-4a3b-9a87-d3e06e5afab8", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + + +public class the_Great_Gubal_Library_Hard +{ + const string NoteStr = + """ + v0.0.0.2 + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. Boss1的击退感觉没有必要画所以没有画 + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + 2. Boss 1 knockback felt unnecessary to draw, so I just skipped it. + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.60 秘本宝库迦巴勒幻想图书馆 [The Great Gubal Library (Hard)]"; + private const string Version = "0.0.0.2"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + //[UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + //public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useAntiKnockback { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + private Dictionary tileData = new Dictionary + { + { Tiles.leftUpper, (TilesColor.white, new Vector3(375.30f, 0.02f,-165.33f))}, + { Tiles.leftLower, (TilesColor.white, new Vector3(352.68f, 0.02f, -165.33f))}, + { Tiles.rightUpper, (TilesColor.white, new Vector3(375.30f, 0.02f, -142.69f))}, + { Tiles.rightLower, (TilesColor.white, new Vector3(352.68f, 0.02f, -142.69f))} + }; + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + //accessory.Method.SendChat($"/e [DEBUG] {str}"); + accessory.Log.Debug($"[DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + } + + + #region 小怪 + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "恐怖眼 - Terror Eye", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:2352"])] + public void TerrorEye(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2200, $"TerrorEye-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Terror Eye Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:2352"], userControl: false)] + public void TerrorEyeClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"TerrorEye-{ev.SourceId}"); + } + + [ScriptMethod(name: "定罪 - Condemnation", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:912"])] + public void Condemnation(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(7f), 90, 2200, $"Condemnation-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Condemnation Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:912"], userControl: false)] + public void CondemnationClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Condemnation-{ev.SourceId}"); + } + + [ScriptMethod(name: "痛打 - Batter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6538"])] + public void Batter(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(7.5f), 2200, $"Batter-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Batter Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:6538"], userControl: false)] + public void BatterClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Batter-{ev.SourceId}"); + } + + [ScriptMethod(name: "黑暗 - Condemnation", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:928"])] + public void Dark(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(8f), 120, 2200, $"Dark-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Dark Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:928"], userControl: false)] + public void DarkClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Dark-{ev.SourceId}"); + } + + [ScriptMethod(name: "双重碎击 - Double Smash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:618"])] + public void DoubleSmash(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(8f), 120, 2200, $"DoubleSmash-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Double Smash Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:618"], userControl: false)] + public void DoubleSmashClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"DoubleSmash-{ev.SourceId}"); + } + + [ScriptMethod(name: "狂水 - Water III", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:5831"])] + public void WaterIII(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 2700, $"WaterIII-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Water III Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:5831"], userControl: false)] + public void WaterIIIClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"WaterIII-{ev.SourceId}"); + } + + [ScriptMethod(name: "魔法锤 - Magic Hammer", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6544"])] + public void MagicHammer(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 2700, $"MagicHammer-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Magic Hammer Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:6544"], userControl: false)] + public void MagicHammerClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"MagicHammer-{ev.SourceId}"); + } + #endregion + + + #region Boss1 + private static IGameObject? GetBossObject(ScriptAccessory sa, uint BossDataId) + { + return sa.GetByDataId(BossDataId).FirstOrDefault(); + } + + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "停刊 - Discontinue", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6464"])] + public void Discontinue(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20, 16.5f), 3700, $"Discontinue-{ev.SourceId}", sa.Data.DefaultDangerColor, scalemode: ScaleMode.ByTime); + } + + + [ScriptMethod(name: "骇人嚎叫 - Frightful Roar", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6459"])] + public void FrightfulRoar(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(7.5f), 2700, $"Frightful Roar-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "停刊2 - Discontinue2", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6466"])] + public void Discontinue2(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10, 20f), 3700, $"Discontinue2-{ev.SourceId}", sa.Data.DefaultDangerColor, scalemode: ScaleMode.ByTime); + } + + [ScriptMethod(name: "Boss1 Clear", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:410"], userControl: false, suppress: 4000)] + public async void Boss1Clear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw(".*"); + if (isLead) + { + await Task.Delay(3000); + var startpos = new Vector3(100.68f, -8.00f, -0.01f); + var endpos = new Vector3(100.68f, -8.00f, 5f); + + var startpos1 = new Vector3(100.74f, -8.00f, 36.02f); + var endpos1 = new Vector3(95.74f, -8.00f, 36.02f); + + var startpos2 = new Vector3(76.95f, -8.00f, 72.00f); + var endpos2 = new Vector3(80.95f, -8.00f, 72.00f); + + var startpos3 = new Vector3(126.68f, -8.00f, 71.96f); + var endpos3 = new Vector3(126.68f, -8.00f, 65.00f); + + DrawHelper.DrawArrow(sa, startpos, endpos, new Vector2(1, 5f), int.MaxValue, $"Navi", color: sa.Data.DefaultSafeColor); + DrawHelper.DrawArrow(sa, startpos1, endpos1, new Vector2(1, 4f), int.MaxValue, $"Navi1", color: sa.Data.DefaultSafeColor); + DrawHelper.DrawArrow(sa, startpos2, endpos2, new Vector2(1, 4f), int.MaxValue, $"Navi2", color: sa.Data.DefaultSafeColor); + DrawHelper.DrawArrow(sa, startpos3, endpos3, new Vector2(1, 4f), int.MaxValue, $"Navi3", color: sa.Data.DefaultSafeColor); + } + } + + #endregion + + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6469"])] + public void boss2AOE(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Navi.*"); + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "火海 - Sea of Flames", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6504"])] + public void SeaofFlames(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6), 2700, $"SeaofFlames-{ev.EffectPosition}"); + } + + [ScriptMethod(name: "突进 - Rush", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6471"])] + public void Rush(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "连线远离" : "Tether Away"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + } + } + + [ScriptMethod(name: "刻印 - Seal of Night and Day", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0059|0058)$"])] + public void SealofNightandDay(Event ev, ScriptAccessory sa) + { + // 0059 Blue + // 0058 red + if (ev.TargetId == sa.Data.Me) + { + string msg = ""; + if (ev.Id == 0059) + { + msg = language == Language.Chinese ? "站在蓝色地板" : "Stand on Blue tile"; + } else + { + msg = language == Language.Chinese ? "站在红色地板" : "Stand on Red tile"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + } + } + + [ScriptMethod(name: "Boss2 Clear", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:434"], userControl: false, suppress: 4000)] + public async void Boss2Clear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw(".*"); + if (isLead) + { + await Task.Delay(3000); + + var startpos = new Vector3(77.87f, -16.00f, -96.00f); + var endpos = new Vector3(77.87f, -16.00f, -100.00f); + + DrawHelper.DrawArrow(sa, startpos, endpos, new Vector2(1, 5f), int.MaxValue, $"Navi", color: sa.Data.DefaultSafeColor); + } + } + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "地板记录 - tileStore", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(6479|6480)$"])] + public void tileStore(Event ev, ScriptAccessory sa) + { + // 6479 white + // 6480 black + sa.Method.RemoveDraw($"Navi.*"); + if (ev.ActionId == 6479) + { + var keysToUpdate = tileData + .Where(tile => Vector3.Distance(tile.Value.Item2, ev.EffectPosition) < 2f) + .Select(tile => tile.Key) + .ToList(); + + foreach (var key in keysToUpdate) + { + tileData[key] = (TilesColor.white, tileData[key].Item2); + } + } + + if (ev.ActionId == 6480) + { + var keysToUpdate = tileData + .Where(tile => Vector3.Distance(tile.Value.Item2, ev.EffectPosition) < 2f) + .Select(tile => tile.Key) + .ToList(); + + foreach (var key in keysToUpdate) + { + tileData[key] = (TilesColor.black, tileData[key].Item2); + } + } + } + + [ScriptMethod(name: "地板记录1 - tileStore1", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:regex:^(2007508)$"])] + public void tileStore1(Event ev, ScriptAccessory sa) + { + + // 2007508 green + var keysToUpdate = tileData + .Where(tile => Vector3.Distance(tile.Value.Item2, ev.SourcePosition) < 2f) + .Select(tile => tile.Key) + .ToList(); + + foreach (var key in keysToUpdate) + { + tileData[key] = (TilesColor.green, tileData[key].Item2); + } + + } + + [ScriptMethod(name: "爆震之章 - On the Properties of Quakes", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:(6486)"])] + public void Quakes(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "站在白圈上" : "Stand on White tile"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 6700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + foreach (var tile in tileData) + { + if (tile.Value.Item1 == TilesColor.white) + { + DrawHelper.DrawCircle(sa, tile.Value.Item2, new Vector2(6f), 6700, $"Quakes", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + } + } + + [ScriptMethod(name: "龙卷之章 - On the Properties of Tornados", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:(6487)"])] + public void Tornados(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "站在黑圈上" : "Stand on Black tile"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 6700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + foreach (var tile in tileData) + { + if (tile.Value.Item1 == TilesColor.black) + { + DrawHelper.DrawCircle(sa, tile.Value.Item2, new Vector2(4f), 6700, $"Tornados", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + } + } + + [ScriptMethod(name: "河童之章 - On the Properties of Imps", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:(6489)"])] + public void Imps(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "站在绿圈上" : "Stand on Green tile"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 6700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + foreach (var tile in tileData) + { + if (tile.Value.Item1 == TilesColor.green) + { + DrawHelper.DrawCircle(sa, tile.Value.Item2, new Vector2(4f), 6700, $"Imps", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + } + } + private enum TilesColor + { + white = 0, + green = 1, + black = 2, + } + + private enum Tiles + { + leftUpper = 0, + leftLower = 1, + rightUpper = 2, + rightLower = 3, + } + + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:6485"])] + public void boss3AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + #endregion + + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } +} + +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; + } + } +} +#region 计算函数 + +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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, MarkType.Attack1, true); + sa.Method.Mark(0xE000000, MarkType.Attack2, true); + sa.Method.Mark(0xE000000, MarkType.Attack3, true); + sa.Method.Mark(0xE000000, MarkType.Attack4, true); + sa.Method.Mark(0xE000000, MarkType.Attack5, true); + sa.Method.Mark(0xE000000, MarkType.Attack6, true); + sa.Method.Mark(0xE000000, MarkType.Attack7, true); + sa.Method.Mark(0xE000000, MarkType.Attack8, true); + sa.Method.Mark(0xE000000, MarkType.Bind1, true); + sa.Method.Mark(0xE000000, MarkType.Bind2, true); + sa.Method.Mark(0xE000000, MarkType.Bind3, true); + sa.Method.Mark(0xE000000, MarkType.Stop1, true); + sa.Method.Mark(0xE000000, MarkType.Stop2, true); + sa.Method.Mark(0xE000000, MarkType.Square, true); + sa.Method.Mark(0xE000000, MarkType.Circle, true); + sa.Method.Mark(0xE000000, MarkType.Cross, true); + sa.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 diff --git a/VeeverSW/04-Stormblood/empty.md b/VeeverSW/04-Stormblood/empty.md new file mode 100644 index 0000000..e69de29 diff --git a/VeeverSW/05-Shadowbringers/DohnMheg.cs b/VeeverSW/05-Shadowbringers/DohnMheg.cs new file mode 100644 index 0000000..450aa90 --- /dev/null +++ b/VeeverSW/05-Shadowbringers/DohnMheg.cs @@ -0,0 +1,276 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; + +namespace Veever.Shadowbringers.DohnMheg; + +[ScriptType(name: "LV.73 水妖幻园多恩美格禁园", territorys: [821], guid: "d8fbc4be-b2c3-43a5-93f7-2901b40d0921", + version: "0.0.0.8", author: "Veever", note: noteStr)] + +public class DohnMheg +{ + const string noteStr = + """ + v0.0.0.8: + 1. 现在支持文字横幅/TTS开关 + 2. 以前的这几个脚本的底层扩展目前懒得重构(就能加啥随便加了) + 鸭门。 + """; + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + #region 小怪 + [ScriptMethod(name: "浇水", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15786"])] + public void WateringWheel(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("打断禁园水妖", duration: 5000, true); + if (isTTS) accessory.Method.TTS("打断禁园水妖"); + } + + + [ScriptMethod(name: "未终针", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15794"])] + public void UnfinalSting(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "未终针"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(3,8); + dp.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "碎骨拳", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15787"])] + public void StraightPunch(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("小死刑", duration: 2700, true); + if (isTTS) accessory.Method.TTS("小死刑"); + } + + #endregion + + + #region Boss1 + + [ScriptMethod(name: "Boss1死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:8857"])] + public void Boss1Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + if (isTTS) accessory.Method.TTS("死刑准备"); + } + + [ScriptMethod(name: "Boss1AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15813"])] + public void Boss1AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4000, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + + + [ScriptMethod(name: "Boss1水脉乱打(太难摆烂)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:8800"])] + public void Boss1Landsblood(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "水脉乱打"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.SourcePosition(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 1000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + + [ScriptMethod(name: "Boss1分摊", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:003E"])] + public void Boss1Stack(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Boss1分摊"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 5500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"与{tname}分摊"); + } + #endregion + + #region Boss2 + [ScriptMethod(name: "Boss2AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:8915"])] + public void Boss2AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4000, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "Boss2召唤养分", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:8897"])] + public void Boss2Fodder(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("接一根连在Boss上的线", duration: 8000, true); + if (isTTS) accessory.Method.TTS("接一根连在Boss上的线"); + } + + #endregion + + #region Boss3 + [ScriptMethod(name: "Boss3死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:13732"])] + public void Boss3Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + if (isTTS) accessory.Method.TTS("死刑准备"); + } + + [ScriptMethod(name: "Boss3AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:13708"])] + public void Boss3AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "Boss3河童歌唱队", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:13552"])] + public void Boss3ImpChoir(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("背对Boss", duration: 3700, true); + if (isTTS) accessory.Method.TTS("背对Boss"); + } + + [ScriptMethod(name: "Boss3青蛙歌唱队", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:13551"])] + public void Boss3ToadChoir(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("去Boss身后", duration: 3700, true); + if (isTTS) accessory.Method.TTS("去Boss身后"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Boss3青蛙歌唱队"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(21); + dp.Radian = float.Pi / 180 * 150; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Boss3终章", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15723"])] + public void Boss3Finale(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("过完独木桥后,进入圈内攻击梦幻的弦乐器", duration: 15000, true); + if (isTTS) accessory.Method.TTS("过完独木桥后,进入圈内攻击乐器"); + } + + [ScriptMethod(name: "Boss3腐蚀咬", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:13547"])] + public void Boss3CorrosiveBile(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离Boss正面", duration: 3700, true); + if (isTTS) accessory.Method.TTS("远离Boss正面"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Boss3腐蚀咬"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(20); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Boss3触手轰击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:13952"])] + public void Boss3FlailingTentacles(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离Boss四角", duration: 4700, true); + if (isTTS) accessory.Method.TTS("远离Boss四角"); + + float iAng = 45; + float aIncrement = 90; + + for (var i = 0; i < 4; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Boss3腐蚀咬:{i}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(20); + dp.Radian = float.Pi / 180 * 50; + dp.Rotation = float.Pi / 180 * (iAng + i * aIncrement); + dp.DestoryAt = 4700; + + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + #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 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"]); + } +} + + diff --git a/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs b/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs new file mode 100644 index 0000000..cb30c78 --- /dev/null +++ b/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs @@ -0,0 +1,752 @@ +using Dalamud.Game.ClientState.Objects.Types; +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using FFXIVClientStructs; +using System; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using FFXIVClientStructs.FFXIV.Client.UI; +using Lumina.Data.Structs; + +namespace Veever.Shadowbringers.Edens_Verse_Iconoclasm; +//^(?!.*((武僧|机工士|龙骑士|学者|舞者|蝰蛇剑士|暗黑骑士|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast|Cancel|Add))).*$ + +[ScriptType(name: "LV.80 伊甸希望乐园 共鸣之章3", territorys: [904], guid: "7732767d-bfb3-4c96-9719-962ba10cec08", + version: "0.0.0.2", author: "Veever", note: noteStr)] + +public class Edens_Verse_Iconoclasm +{ + const string noteStr = + """ + v0.0.0.2: + 1. 此脚本只用了一个arr回放进行测试,如果出现错画的问题,请dc@我并提供arr回放 + 鸭门。 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + public int LightsCourseCount; + public int StackCount; + + public List BallCheckList = new List + { + //From N (left to Right) + new Vector3(85.00f, 0.00f, 75.00f), //[0] yes + new Vector3(95.00f, 0.00f, 75.00f), //[1] yes + new Vector3(105.00f, 0.00f, 75.00f), //[2] yes + new Vector3(115.00f, 0.00f, 75.00f), //[3] yes + }; + + public List BallStartList = new List + { + //From N (left to Right) + new Vector3(85.00f, 0.00f, 80.00f), //[0] yes + new Vector3(95.00f, 0.00f, 80.00f), //[1] yes + new Vector3(105.00f, 0.00f, 80.00f), //[2] yes + new Vector3(115.00f, 0.00f, 80.00f), //[3] yes + + new Vector3(80.00f, 0.00f, 115.00f), //[4] + new Vector3(80.00f, 0.00f, 105.00f), //[5] + new Vector3(80.00f, 0.00f, 95.00f), //[6] + new Vector3(80.00f, 0.00f, 85.00f), //[7] + }; + + public List centerPoint = new List + { + //From N (left to Right) + new Vector3(85.00f, 0.00f, 100.00f), //[0] yes + new Vector3(95.00f, 0.00f, 100.00f), //[1] yes + new Vector3(105.00f, 0.00f, 100.00f), //[2] yes + new Vector3(115.00f, 0.00f, 100.00f), //[3] yes + + //From W (left to Right) + new Vector3(100.00f, 0.00f, 115.00f), //[4] + new Vector3(100.00f, 0.00f, 105.00f), //[5] + new Vector3(100.00f, 0.00f, 95.00f), //[6] + new Vector3(100.00f, 0.00f, 85.00f), //[7] + }; + + private readonly object LightsCourseLock = new object(); + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + LightsCourseCount = 0; + StackCount = 0; + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] + public void debug(Event @event, ScriptAccessory accessory) + { + var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); + if (myself == null) return; + var buffId = myself.HasStatus(2238) ? 2238 : 2239; + DebugMsg($"buffId: {buffId}", accessory); + } + + + [ScriptMethod(name: "虚无波动", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(19538|20052)$"])] + public void EmptyWave(Event @event, ScriptAccessory accessory) + { + if (@event.ActionId() == 19538) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 3500, true); + } else + { + if (isText) accessory.Method.TextInfo("大AOE", duration: 3500, true); + } + + } + + [ScriptMethod(name: "暗光钉", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0025"])] + public void UnshadowedStake(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo($"引导激光至场外", duration: 4500, true); + } else + { + if (isText) accessory.Method.TextInfo($"远离连线点名", duration: 4500, true); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Unshadowed Stake"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.TargetObject = @event.TargetId(); + dp.Scale = new Vector2(4, 80); + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "白光&黑暗奔流", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(19516|20067|19518|19517|19491|19490|19521)$"])] + public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) + { + lock (LightsCourseLock) + { + DebugMsg($"LightsCourseCount: {LightsCourseCount}", accessory); + if (LightsCourseCount == 0 || LightsCourseCount == 1) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 0 & 1"); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 4000, + "Light's Course 0 & 1 SAFE", new Vector4(0, 1, 0, 1), 6800); + } + + if (LightsCourseCount == 2 || LightsCourseCount == 3) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 2 & 3"); + } + + if (LightsCourseCount == 4 || LightsCourseCount == 5) + { + DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); + if (@event["ActionId"] == "20067") + { + DebugMsg($"{LightsCourseCount}: In 20067", accessory); + var index0 = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + if (index0 == 1) + { + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 4 & 5", delay: 6700); + } else if (index0 == 2) + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 4 & 5", delay: 6700); + } + DebugMsg($"is drawing 20067", accessory); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "Light's Course 4 & 5"); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 4000, + "Light's Course 0 & 1 SAFE", new Vector4(0, 1, 0, 1), 6800); + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 4 & 5"); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 4000, + "Light's Course 0 & 1 SAFE", new Vector4(0, 1, 0, 1), 6800); + } + } + + if (LightsCourseCount == 6 || LightsCourseCount == 7) + { + DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); + if (@event["ActionId"] == "20067") + { + DebugMsg($"{LightsCourseCount}: In 20067", accessory); + var index0 = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + if (index0 == 1) + { + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); + } else if (index0 == 2) + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); + } + DebugMsg($"is drawing 20067", accessory); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "Light's Course 6 & 7"); + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 6 & 7"); + } + } + + if (LightsCourseCount == 8 || LightsCourseCount == 9 || LightsCourseCount == 10 || LightsCourseCount == 11) + { + DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); + if (LightsCourseCount == 11) + { + accessory.Method.TextInfo("优先躲避红色", duration: 4700, true); + } + if (@event["ActionId"] == "19518") + { + DebugMsg($"{LightsCourseCount}: In 19518", accessory); + var index0 = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + if (index0 == 1) + { + if (LightsCourseCount == 8 || LightsCourseCount == 9) + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + } else + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + } + } else if (index0 == 2) + { + if (LightsCourseCount == 8 || LightsCourseCount == 9) + { + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + } else + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + } + } + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11"); + if (LightsCourseCount == 8 || LightsCourseCount == 9) + { + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 2000, + "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), 6800); + } + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11"); + if (LightsCourseCount == 8 || LightsCourseCount == 9) + { + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 2000, + "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), 6800); + } + } + } + + if (LightsCourseCount == 12 || LightsCourseCount == 13 || LightsCourseCount == 14 || + LightsCourseCount == 15 || LightsCourseCount == 16 || + LightsCourseCount == 17 || LightsCourseCount == 18 || LightsCourseCount == 19 || + LightsCourseCount == 20 || LightsCourseCount == 21 || LightsCourseCount == 22 || + LightsCourseCount == 23 || LightsCourseCount == 28 || LightsCourseCount == 29 || + LightsCourseCount == 30 || LightsCourseCount == 31) + { + // 2238 Light //19516 & 19490 // 0x8BE + // 2239 Darkness //19517 & 19491 // 0x8BF + DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); + var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); + if (myself == null) return; + var buffId = myself.HasStatus(2238) ? 2238 : 2239; + DebugMsg($"buffId: {buffId}", accessory); + if (@event["ActionId"] == "19516" || @event["ActionId"] == "19490") + { + DebugMsg($"{LightsCourseCount}: In 19516", accessory); + if (buffId == 2238) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(1, 0, 0, 1)); + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(0, 1, 0, 1)); + } + } else if (@event["ActionId"] == "19517" || @event["ActionId"] == "19491") + { + DebugMsg($"{LightsCourseCount}: In 19517", accessory); + if (buffId == 2239) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(1, 0, 0, 1)); + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(0, 1, 0, 1)); + } + } + } + + if (LightsCourseCount == 24 || LightsCourseCount == 25 || LightsCourseCount == 26 || LightsCourseCount == 27) + { + // 2238 Light //19516 & 19490 // 0x8BE + // 2239 Darkness //19517 & 19491 & 19521 // 0x8BF + DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); + var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); + if (myself == null) return; + var buffId = myself.HasStatus(2238) ? 2238 : 2239; + DebugMsg($"buffId: {buffId}", accessory); + if (@event["ActionId"] == "19521") + { + if (buffId == 2239) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); + if (index == 1) + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + } else if (index == 2) + { + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + } + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1)); + if (index == 1) + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + } else if (index == 2) + { + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + } + } + } else if (@event["ActionId"] == "19516") + { + if (buffId == 2238) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); + if (index == 1) + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + } else if (index == 2) + { + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1), delay: 6700); + } + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1)); + if (index == 1) + { + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + } else if (index == 2) + { + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1), delay: 6700); + } + } + } else + { + if (@event["ActionId"] == "19517") + { + if (buffId == 2239) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1)); + } + } + if (@event["ActionId"] == "19518") + { + if (buffId == 2239) + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1)); + } else + { + var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); + } + } + + } + } + + LightsCourseCount++; + DebugMsg($"LightsCourseCount increased to: {LightsCourseCount}", accessory); + } + } + + + + + + + [ScriptMethod(name: "强制传送", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(2242|2243|2240|2241)$"])] + public void ForcedTransfer(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Forced Transfer"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(1, 15); + switch (@event["StatusID"]) + { + case "2240": + dp.Rotation = 0; + break; + case "2241": + dp.Rotation = float.Pi; + break; + case "2242": + dp.Rotation = float.Pi / 180 * 90; + break; + case "2243": + dp.Rotation = float.Pi / 180 * 270; + break; + } + dp.DestoryAt = 10000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + } + } + + private int FindExactPositionIndex(Vector3 position, List positionList) + { + if (positionList == null || positionList.Count == 0) + return -1; + + for (int i = 0; i < positionList.Count; i++) + { + if (position.X == positionList[i].X && + position.Y == positionList[i].Y && + position.Z == positionList[i].Z) + { + return i; + } + } + return -1; + } + + [ScriptMethod(name: "分组分摊", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:003E"])] + public async void Stack(Event @event, ScriptAccessory accessory) + { + await Task.Delay(50); + if (StackCount == 0) + { + accessory.Method.TextInfo("分组分摊", duration: 4700, true); + accessory.Method.TTS("分组分摊"); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Stack"; + dp.Color = new Vector4(0, 1, 1, 1); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + StackCount++; + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } +} + +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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + +public static class IbcHelper +{ + public static IBattleChara? GetById(ScriptAccessory accessory, uint id) + { + return (IBattleChara?)accessory.Data.Objects.SearchByEntityId(id); + } + + public static IBattleChara? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me) as IBattleChara; + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } + + public static IBattleChara? GetByEntityId(ScriptAccessory accessory, uint id) + { + return (IBattleChara?)accessory.Data.Objects.SearchByEntityId(id); + } + public static bool HasStatus(this IBattleChara chara, uint statusId) + { + return chara.StatusList.Any(x => x.StatusId == statusId); + } + + public static bool HasStatus(this IBattleChara chara, uint[] statusIds) + { + return chara.StatusList.Any(x => statusIds.Contains(x.StatusId)); + } +} + diff --git a/VeeverSW/05-Shadowbringers/HolminsterSwitch.cs b/VeeverSW/05-Shadowbringers/HolminsterSwitch.cs new file mode 100644 index 0000000..fa8df13 --- /dev/null +++ b/VeeverSW/05-Shadowbringers/HolminsterSwitch.cs @@ -0,0 +1,308 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; +namespace Veever.Shadowbringers.HolminsterSwitch; + +[ScriptType(name: "LV.71 遇袭集落水滩村", territorys: [837], guid: "a407d364-b2bd-4e12-9332-70ca3829ece7", + version:"0.0.1.0", author: "Veever", note: noteStr)] + +public class HolminsterSwitch +{ + const string noteStr = + """ + v0.0.1.0: + 1. 现在支持文字横幅/TTS开关 + 2. 以前的这几个脚本的底层扩展目前懒得重构(就能加啥随便加了) + 鸭门。 + """; + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + private static bool ParseObjectId(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; + } + } + + #region Boss1 + + [ScriptMethod(name: "Boss1AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15813"])] + public void Boss1AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4000, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "Boss1钢铁", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15816"])] + public void Boss1GibbetCage(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("钢铁,远离Boss", duration: 3000, true); + + var dp = accessory.Data.GetDefaultDrawProperties(); + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + + dp.Name = "GibbetCage"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = tid; + dp.Scale = new Vector2(8); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isTTS) accessory.Method.TTS("钢铁,远离Boss"); + } + + [ScriptMethod(name: "Boss1螺旋突刺", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15814"])] + public void Boss1Thumbscrew(Event @event, ScriptAccessory accessory) + { + var tarp = JsonConvert.DeserializeObject(@event["EffectPosion"]); + var dp = accessory.Data.GetDefaultDrawProperties(); + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + + dp.Name = "Thumbscrew"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = tid; + dp.TargetPosition = tarp; + dp.Scale = new Vector2(10, 5); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "Boss1死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15812"])] + public void Boss1Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + if (isTTS) accessory.Method.TTS("死刑准备"); + } + + + + #endregion + + + #region Boss2 + + [ScriptMethod(name: "Boss2死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15823"])] + public void Boss2Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + if (isTTS) accessory.Method.TTS("死刑准备"); + } + + + [ScriptMethod(name: "Boss2AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15824"])] + public void Boss2AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4000, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "Boss2麻将点名", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15829"])] + public void Boss2FeveredFlagellation(Event @event, ScriptAccessory accessory) + { + + //var bossPosition = JsonConvert.DeserializeObject(@event["EffectPosion"] ); + + //if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + //for (int i = 0; i < 4; i++) + //{ + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = "connect"; + // dp.Scale = new Vector2(5, 5); + // dp.Color = accessory.Data.DefaultDangerColor; + // dp.Owner = accessory.Data.PartyList[i]; + // dp.TargetPosition = bossPosition; + // dp.DestoryAt = 16000; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + //} + + if (isText) accessory.Method.TextInfo("分散,不要重叠", duration: 8000, true); + if (isTTS) accessory.Method.TTS("分散,不要重叠"); + + } + + [ScriptMethod(name: "Boss2分摊", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15826"])] + public void Boss2Exorcise(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + if (isText) accessory.Method.TextInfo($"躲避圆圈并与 {tname} 分摊", duration: 4000, true); + + var dp = accessory.Data.GetDefaultDrawProperties(); + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + + dp.Name = "Exorcise"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = tid; + dp.Scale = new Vector2(6); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isTTS) accessory.Method.TTS($"躲避圆圈并与 {tname} 分摊"); + } + + #endregion + + #region Boss3 + [ScriptMethod(name: "Boss3死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15831"])] + public void Boss3Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + if (isTTS) accessory.Method.TTS("死刑准备"); + } + + [ScriptMethod(name: "Boss3AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15832"])] + public void Boss3AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4000, true); + + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "Boss3钟摆", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16777"])] + public void Boss3Pendulum(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离坦克和场中", duration: 4000, true); + if (isTTS) accessory.Method.TTS("远离坦克和场中"); + var midPoint = new Vector3(134, 23, -465); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "远离场中"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.DestoryAt = 8000; + dp.Position = midPoint; + dp.Scale = new Vector2(16); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Boss3核爆", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0057"])] + public void Boss3PendulumTank(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + dp.Name = "Boss3核爆"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 2000; + dp.DestoryAt = 6000; + dp.Owner = tid; + dp.Scale = new Vector2(22); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Boss3束缚", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:1849"])] + public void Boss3ChainDown(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + //accessory.Method.Mark(tid, KodakkuAssist.Module.GameOperate.MarkType.Attack1, true); + + if (isText) accessory.Method.TextInfo("攻击锁链", duration: 4000, true); + if (isTTS) accessory.Method.TTS("攻击锁链"); + } + + [ScriptMethod(name: "Boss3鞭打", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(1584[67])$"])] + public void Boss3Knout(Event @event, ScriptAccessory accessory) + { + var aid = JsonConvert.DeserializeObject(@event["ActionId"]); + var isR = aid == 15846; + + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + float.TryParse(@event["SourceRotation"], out var rotation); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "鞭打"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.DestoryAt = 5500; + dp.Owner = sid; + dp.Scale = new Vector2(24); + dp.Radian = float.Pi / 180 * 210; + dp.Rotation = float.Pi / 180 * 75 * (isR ? -1 : 1); + + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + if (isText) accessory.Method.TextInfo($"去Boss{(isR ? "左" : "右")}面", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"去Boss{(isR ? "左" : "右")}面"); + } + + [ScriptMethod(name: "Boss3分摊", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:15844"])] + public void Boss3IntoTheLight(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + if (isText) accessory.Method.TextInfo($"与 {tname} 分摊", duration: 4000, true); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "IntoTheLight"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = sid; + dp.TargetObject = tid; + dp.Scale = new Vector2(6, 20); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + if (isTTS) accessory.Method.TTS($"与 {tname} 分摊"); + } + + + [ScriptMethod(name: "激烈捶打", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(1583(6|7|9))$"])] + public void Boss3FierceBeating(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("跟随身后扇形躲避", duration: 4000, true); + if (isTTS) accessory.Method.TTS("跟随身后扇形躲避"); + //var dp = accessory.Data.GetDefaultDrawProperties(); + //if (!ParseObjectId(@event["SourceId"], out var sid)) return; + //var efp = JsonConvert.DeserializeObject(@event["EffectPosition"]); + + //var aid = JsonConvert.DeserializeObject(@event["ActionId"]); + + //dp.Name = "激烈捶打"; + //dp.Color = accessory.Data.DefaultDangerColor; + //dp.DestoryAt = aid == 15836 ? 3000 : 7000; + //dp.Position = efp; + //dp.Scale = new Vector2(6); + //dp.DestoryAt = 5000; + //accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "九尾猫", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15840"])] + public void Boss3NineTails(Event @event, ScriptAccessory accessory) + { + //var dp = accessory.Data.GetDefaultDrawProperties(); + //if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + //dp.Name = "九尾猫"; + //dp.Color = accessory.Data.DefaultDangerColor; + //dp.DestoryAt = 6000; + //dp.Owner = sid; + //dp.Scale = new Vector2(20); + //dp.Radian = float.Pi / 180 * 120; + //dp.Rotation = float.Pi; + //accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + + } + #endregion +} \ No newline at end of file diff --git a/VeeverSW/05-Shadowbringers/Malikah-s-Well.cs b/VeeverSW/05-Shadowbringers/Malikah-s-Well.cs new file mode 100644 index 0000000..ff85d3c --- /dev/null +++ b/VeeverSW/05-Shadowbringers/Malikah-s-Well.cs @@ -0,0 +1,1882 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace Veever.Shadowbringers.Malikah_s_Well; + +[ScriptType(name: Name, territorys: [836], guid: "41547ef2-b54c-4296-9ca4-8e54992ea6fb", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class Malikah_s_Well +{ + const string NoteStr = + """ + v0.0.0.2 + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.77 避暑离宫马利卡大井 [Malikahs Well]"; + private const string Version = "0.0.0.2"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useAntiKnockback { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + public int HereticsForkcount = 0; + public int BreakingWheelCount = 0; + + private readonly object CountLock = new object(); + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + HereticsForkcount = 0; + BreakingWheelCount = 0; + } + + + #region 小怪 + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void destoryCancelAction(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($".*{ev.TargetId}"); + } + + #region boss1前 + [ScriptMethod(name: "地表碎裂 - Earthshatter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16261"])] + public void Earthshatter(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 12f), 2700, $"Earthshatter-{ev.SourceId}", sa.Data.DefaultDangerColor, scalemode: ScaleMode.ByTime); + } + + [ScriptMethod(name: "Earthshatter Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16261"], userControl: false)] + public void EarthshatterClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Earthshatter-{ev.SourceId}"); + } + #endregion + + #region boss2前 + [ScriptMethod(name: "自爆 - Self-destruct", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16263"])] + public void SelfDestruct(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(6.9f), 2700, $"SelfDestruct-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Self-destruct Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16263"], userControl: false)] + public void SelfDestructClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"SelfDestruct-{ev.SourceId}"); + } + + [ScriptMethod(name: "投石 - Pebble", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16262"])] + public void Pebble(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(3f), 2200, $"Pebble-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Pebble Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16262"], userControl: false)] + public void PebbleClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Pebble-{ev.SourceId}"); + } + + #endregion + + #region boss3前 + [ScriptMethod(name: "震撼领域 - Realm Shaker", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16266"])] + public void RealmShaker(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Realm Shaker-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Realm Shaker Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16266"], userControl: false)] + public void RealmShakerClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Realm Shaker-{ev.SourceId}"); + } + + [ScriptMethod(name: "称誉 - Acclaim", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16265"])] + public void Acclaim(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(12f), 120, 2700, $"Acclaim-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Acclaim Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16265"], userControl: false)] + public void AcclaimClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Acclaim-{ev.SourceId}"); + } + + + + + #endregion + + #endregion + + + #region Boss1 + private static IGameObject? GetBossObject(ScriptAccessory sa, uint BossDataId) + { + return sa.GetByDataId(BossDataId).FirstOrDefault(); + } + + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15589"])] + public void boss1Tankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名, 注意减伤" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "落石 - Falling Rock", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15594"])] + public void FallingRock(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(4f), 2700, $"FallingRock-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "甩尾锤 - Head Toss", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15590"])] + public void HeadToss(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + + if (ev.TargetId == sa.Data.Me) + { + msg = language == Language.Chinese ? "分摊点名" : "Stack"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(5f), 4700, $"HeadToss-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + + + [ScriptMethod(name: "大回旋 - Right Round", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15591"])] + public void RightRound(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"钢铁远离" : $"Chariot (Out) "; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 7200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(8.5f), 2200, $"RightRound-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "重锤碎击 - Flail Smash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15593"])] + public void FlailSmash(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(11f), 2200, $"RightRound-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + #endregion + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + + //[ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44331"])] + //public void boss2AOE(Event ev, ScriptAccessory sa) + //{ + // string msg = language == Language.Chinese ? "AOE" : "AOE"; + // if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + // if (isTTS) sa.Method.TTS($"{msg}"); + //} + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15595"])] + public void boss2Tankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名, 注意减伤" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "钻井 - Wellbore", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15597"])] + public void Wellbore(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(15f), 6700, $"Wellbore-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "井水喷出 - Geyser Eruption", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15598"])] + public void GeyserEruption(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 3200, $"GeyserEruption-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "钻井绘制 - Geysers Draw", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2009801", "Operate:Add"])] + public void GeysersDraw(Event ev, ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug("In GeysersDraw"); + DrawHelper.DrawCircle(sa, ev.SourcePosition, new Vector2(4f), 120000, $"GeysersDraw-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false, drawmode: DrawModeEnum.Imgui); + } + + [ScriptMethod(name: "Geysers Clear", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2009801", "Operate:Remove"])] + public void GeysersClear(Event ev, ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug("In GeysersClear"); + sa.Method.RemoveDraw($"GeysersDraw-{ev.SourceId}"); + } + + [ScriptMethod(name: "高压 - High Pressure", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(15596)$"])] + public async void HighPressure(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "远离钻井击退" : "Knockback away from Danger Zone"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "HighPressure"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetPosition = ev.SourcePosition; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, 20); + dp.DestoryAt = 3700; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + await Task.Delay(4200); + string msg1 = language == Language.Chinese ? "前往Boss身后" : "Move behind boss"; + if (isText) sa.Method.TextInfo($"{msg1}", duration: 3700, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + } + + [ScriptMethod(name: "强制放水 - Swift Spill", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(009C|009D)$"])] + public void SwiftSpill(Event ev, ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug("In SwiftSpill"); + + var degree = 60f; + if (isDebug) sa.Log.Debug($"{ev["Id"]}"); + if (ev["Id"] == "009C") + { + if (isDebug) sa.Log.Debug("Is 0x009C"); + degree = -60f; + } + + var index = 0f; + var boss = IbcHelper.GetById(sa, ev.SourceId); + if (boss == null) return; + if (isDebug) sa.Log.Debug($"bossRot:{boss.Rotation}"); + + for (var i = 0; i < 6; i++) + { + if (i == 0) + { + DrawHelper.DrawFan(sa, boss.Position, boss.Rotation, new Vector2(22f), 60, 6700, $"SwiftSpill-{ev.SourceId}-i:{i}-deg:{degree}", sa.Data.DefaultDangerColor); + } + else + { + DrawHelper.DrawFan(sa, boss.Position, boss.Rotation + MathTools.DegToRad(index + i * degree), new Vector2(22f), 60, 3800, $"SwiftSpill-{ev.SourceId}-i:{i}-deg:{index + i * degree}", sa.Data.DefaultDangerColor, delay: 3700 + i * 1000); + } + + } + } + + + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:(15601)"])] + public void boss3AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 3500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + HereticsForkcount = 0; + BreakingWheelCount = 0; + } + + [ScriptMethod(name: "异端十字叉 - Heretic's Fork", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:(15602|15609|15886)"])] + public void HereticsFork(Event ev, ScriptAccessory sa) + { + lock (CountLock) + { + switch (ev.ActionId) + { + case 15602: + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10, 40f), 4700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 20)); + DrawHelper.DrawRectObjectNoTargetWithRot(sa, ev.SourceId, new Vector2(10, 40f), float.Pi / 2, 4700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(20, 0, 0)); + break; + case 15609: + if (HereticsForkcount == 0) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 60f), 7700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 30)); + DrawHelper.DrawRectObjectNoTargetWithRot(sa, ev.SourceId, new Vector2(10f, 60f), float.Pi / 2, 7700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(30, 0, 0)); + HereticsForkcount++; + break; + } else + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 60f), 3700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 30), delay: 4000); + DrawHelper.DrawRectObjectNoTargetWithRot(sa, ev.SourceId, new Vector2(10f, 60f), float.Pi / 2, 3700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(30, 0, 0), delay: 4000); + HereticsForkcount++; + break; + } + case 15886: + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10, 40f), 4700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 20), delay: 20000); + DrawHelper.DrawRectObjectNoTargetWithRot(sa, ev.SourceId, new Vector2(10, 40f), float.Pi / 2, 4700, $"HereticsFork-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(20, 0, 0), delay: 20000); + break; + } + } + } + + [ScriptMethod(name: "碎轮 - Breaking Wheel", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:(15605|15610|15887)"])] + public void BreakingWheel(Event ev, ScriptAccessory sa) + { + lock (CountLock) + { + switch (ev.ActionId) + { + case 15605: + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(5f), 4700, $"BreakingWheel-{ev.SourceId}", sa.Data.DefaultSafeColor, scaleByTime: false); + if (isLead) DrawHelper.DrawDisplacement(sa, ev.SourcePosition, new Vector2(1f), 4700, $"BreakingWheelNavi-{ev.SourceId}", sa.Data.DefaultSafeColor); + break; + case 15610: + if (BreakingWheelCount == 0) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(5f), 8700, $"BreakingWheel-{ev.SourceId}", sa.Data.DefaultSafeColor, scaleByTime: false); + if (isLead) DrawHelper.DrawDisplacement(sa, ev.SourcePosition, new Vector2(1f), 8700, $"BreakingWheelNavi-{ev.SourceId}", sa.Data.DefaultSafeColor); + BreakingWheelCount++; + break; + } else + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(5f), 4000, $"BreakingWheel-{ev.SourceId}", sa.Data.DefaultSafeColor, scaleByTime: false, delay: 4700); + if (isLead) DrawHelper.DrawDisplacement(sa, ev.SourcePosition, new Vector2(1f), 4000, $"BreakingWheelNavi-{ev.SourceId}", sa.Data.DefaultSafeColor, delay: 4700); + BreakingWheelCount++; + break; + } + + case 15887: + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(5f), 4000, $"BreakingWheel-{ev.SourceId}", sa.Data.DefaultSafeColor, scaleByTime: false, delay: 24700); + if (isLead) DrawHelper.DrawDisplacement(sa, ev.SourcePosition, new Vector2(1f), 4000, $"BreakingWheelNavi-{ev.SourceId}", sa.Data.DefaultSafeColor, delay: 24700); + break; + } + } + } + #endregion + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } +} + +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; + } + } + + /// + /// 获取对象头顶的 Icon ID + /// + public static uint GetObjectIconId(IGameObject? obj) + { + if (obj == null || !obj.IsValid()) return 0; + + unsafe + { + var gameObj = (GameObject*)obj.Address; + if (gameObj == null) return 0; + + // 直接读取头顶标记/Icon ID + return gameObj->NamePlateIconId; + } + } + + /// + /// 检查对象是否有指定的 Icon ID + /// + public static bool HasIconId(IGameObject? obj, int iconId) + { + return GetObjectIconId(obj) == iconId; + } + +} +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, MarkType.Attack1, true); + sa.Method.Mark(0xE000000, MarkType.Attack2, true); + sa.Method.Mark(0xE000000, MarkType.Attack3, true); + sa.Method.Mark(0xE000000, MarkType.Attack4, true); + sa.Method.Mark(0xE000000, MarkType.Attack5, true); + sa.Method.Mark(0xE000000, MarkType.Attack6, true); + sa.Method.Mark(0xE000000, MarkType.Attack7, true); + sa.Method.Mark(0xE000000, MarkType.Attack8, true); + sa.Method.Mark(0xE000000, MarkType.Bind1, true); + sa.Method.Mark(0xE000000, MarkType.Bind2, true); + sa.Method.Mark(0xE000000, MarkType.Bind3, true); + sa.Method.Mark(0xE000000, MarkType.Stop1, true); + sa.Method.Mark(0xE000000, MarkType.Stop2, true); + sa.Method.Mark(0xE000000, MarkType.Square, true); + sa.Method.Mark(0xE000000, MarkType.Circle, true); + sa.Method.Mark(0xE000000, MarkType.Cross, true); + sa.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 diff --git a/VeeverSW/05-Shadowbringers/MtGulg.cs b/VeeverSW/05-Shadowbringers/MtGulg.cs new file mode 100644 index 0000000..dd6502a --- /dev/null +++ b/VeeverSW/05-Shadowbringers/MtGulg.cs @@ -0,0 +1,1975 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml.Linq; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.Shadowbringers.MtGulg; + +[ScriptType(name: Name, territorys: [822], guid: "e67816ed-08b7-4df6-857a-2b71e9426d1c", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class MtGulg +{ + const string NoteStr = + """ + v0.0.0.3 + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.79 伪造天界格鲁格火山 [Mt. Gulg]"; + private const string Version = "0.0.0.3"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useAntiKnockback { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + [UserSetting("目标标记开关(Target Marker toggle)")] + public bool isMark { get; set; } = true; + + [UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + + private readonly object CountLock = new object(); + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + } + + + #region 小怪 + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void destoryCancelAction(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($".*{ev.TargetId}"); + } + + #region boss1前 + [ScriptMethod(name: "祭神 - Immolation", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16810"])] + public void Immolation(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Immolation-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Immolation Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16810"], userControl: false)] + public void ImmolationClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Immolation-{ev.SourceId}"); + } + + [ScriptMethod(name: "尾部碎击 - Tail Smash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16811"])] + public void TailSmash(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, float.Pi, new Vector2(15f), 120, 3200, $"Tail Smash-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Tail Smash Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16811"], userControl: false)] + public void TailSmashClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Tail Smash-{ev.SourceId}"); + } + + [ScriptMethod(name: "天堂斩 - Heavenslash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16809"])] + public void Heavenslash(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(8f), 90, 2700, $"Heavenslash-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Heavenslash Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16809"], userControl: false)] + public void HeavenslashClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Heavenslash-{ev.SourceId}"); + } + #endregion + + #region boss2前 + [ScriptMethod(name: "祝圣之光 - Sanctification", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16814"])] + public void Sanctification(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(12f), 90, 4700, $"Sanctification-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + + [ScriptMethod(name: "Sanctification Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16814"], userControl: false)] + public void SanctificationClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Sanctification-{ev.SourceId}"); + } + + [ScriptMethod(name: "惩罚之光 - Punitive Light", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16815"])] + public void PunitiveLight(Event ev, ScriptAccessory sa) + { + string msg = ""; + if (isMark) + { + msg = language == Language.Chinese ? "打断被标记目标" : "Interrupt marked target"; + } + else + { + msg = language == Language.Chinese ? "打断得到宽恕的偏见" : "Interrupt forgiven prejudice"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + if (isMark) sa.Method.Mark((uint)ev.TargetId, KodaMarkType.Bind1, LocalMark); + } + + #endregion + + #region boss3前 + [ScriptMethod(name: "辉煌右手 - Right Palm", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(16247|16248)$"])] + public void RightPalm(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(15f, 30f), 4200, $"Right Palm-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(7.5f, 0, 0)); + } + + [ScriptMethod(name: "辉煌左手 - Left Palm", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(16249|16250)$"])] + public void LeftPalm(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(15f, 30f), 4200, $"Left Palm-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(-7.5f, 0, 0)); + } + #endregion + + #endregion + + #region Boss1 + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15611"])] + public void boss1Tankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名, 注意减伤" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "流明无限 - Lumen Infinitum", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16818"])] + public void LumenInfinitum(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(4.5f, 40f), 3400, $"Lumen Infinitum-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "台风之翼 - Typhoon Wing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(15616|17153)$"])] + public void TyphoonWing(Event ev, ScriptAccessory sa) + { + var duration = ev.ActionId == 15616 ? 4700 : 6700; + DrawHelper.DrawFanObjectNoRot(sa, ev.SourceId, new Vector2(20f), 60, duration, $"Typhoon Wing-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "飓风之翼 - Hurricane Wing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15619"])] + public void HurricaneWing(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, $"Hurricane Wing-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "AOE提示boss1 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15613"])] + public void Boss1AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + #endregion + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15627"])] + public void boss2AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15595"])] + public void boss2Tankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名, 注意减伤" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "解经 - Exegesis", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(16989|15622|16988)$"])] + public void Exegesis(Event ev, ScriptAccessory sa) + { + List blocks16989 = new List() + { + new Vector3(-240f, 210f, -55f), // mid -> up + new Vector3(-250f, 210f, -45f), // upperRight -> up + new Vector3(-250f, 210f, -65f), // lowerRight -> up + new Vector3(-230f, 210f, -65f), // lowerLeft -> up + new Vector3(-230f, 210f, -45f), // upperLeft -> up + }; + + List blocks15622 = new List() + { + new Vector3(-240f, 210f, -45f), // midupper -> up + new Vector3(-240f, 210f, -65f), // midLower -> up + }; + + List blocks16988 = new List() + { + new Vector3(-230f, 210f, -55f), // left -> up + new Vector3(-250f, 210f, -55f), // right -> up + new Vector3(-240f, 210f, -55f), // mid -> up + new Vector3(-240f, 210f, -45f), // midupper -> up + new Vector3(-240f, 210f, -65f), // midLower -> up + }; + + + switch (ev.ActionId) + { + case 16989: + foreach (var pos in blocks16989) + { + DrawHelper.DrawRectPosNoTarget(sa, pos, new Vector2(10f, 10f), 5000, $"Exegesis-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + break; + case 15622: + foreach (var pos in blocks15622) + { + DrawHelper.DrawRectPosNoTarget(sa, pos, new Vector2(10f, 10f), 5000, $"Exegesis-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + break; + case 16988: + foreach (var pos in blocks16988) + { + DrawHelper.DrawRectPosNoTarget(sa, pos, new Vector2(10f, 10f), 5000, $"Exegesis-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + break; + } + } + + [ScriptMethod(name: "踩塔提示 - boss2 Tower", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2009807"])] + public void boss2Tower(Event ev, ScriptAccessory sa) + { + if (ev["Operate"] == "Add") + { + DrawHelper.DrawCircle(sa, ev.SourcePosition, new Vector2(5f), 7500, $"boss2Tower-{ev.SourceId}", sa.Data.DefaultSafeColor, scaleByTime: false); + } else + { + sa.Method.RemoveDraw($"boss2Tower-{ev.SourceId}"); + } + } + + [ScriptMethod(name: "忏悔 - Perfect Contrition", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:10189"])] + public void PerfectContrition(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawDount(sa, ev.SourcePosition, new Vector2(15f), new Vector2(5f), 7700, $"PerfectContrition-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "AOE提示boss3 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15636"])] + public void boss3AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "渐弱神音 - Divine Diminuendo", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(15638|15640|15649)$"])] + public void DivineDiminuendo(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + case 15638 or 15649: + { + var bytime = ev.ActionId == 15638; + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 3700, $"Divine Diminuendo-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: bytime); + break; + } + case 15640: + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 3700, $"Divine Diminuendo-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + DrawHelper.DrawDountObject(sa, ev.SourceId, new Vector2(16f), new Vector2(10f), 3700, $"Divine Diminuendo Second Donut-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + DrawHelper.DrawDountObject(sa, ev.SourceId, new Vector2(32f), new Vector2(18f), 3700, $"Divine Diminuendo Second Donut-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + break; + } + } + } + + [ScriptMethod(name: "加强信音 - Conviction Marcato", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(15643|15642|15648)$"])] + public void ConvictionMarcato(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(5f, 40f), 3700, $"Conviction Marcato-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha)); + } + + + //[ScriptMethod(name: "定位Boss - Navi to Boss", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:regex:^(10190)$"])] + //public void Navi(Event ev, ScriptAccessory sa) + //{ + // if (isLead) DrawHelper.DrawDisplacement(sa, ev.SourcePosition, new Vector2(2f), 4700, $"Navi-{ev.SourceId}", sa.Data.DefaultSafeColor); + //} + + #endregion + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 diff --git a/VeeverSW/05-Shadowbringers/the-Grand-Cosmos.cs b/VeeverSW/05-Shadowbringers/the-Grand-Cosmos.cs new file mode 100644 index 0000000..799a5ea --- /dev/null +++ b/VeeverSW/05-Shadowbringers/the-Grand-Cosmos.cs @@ -0,0 +1,1087 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using FFXIVClientStructs; +using System; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +namespace Veever.Shadowbringers.theGrandCosmos; + +[ScriptType(name: "LV.80 魔法宫殿宇宙宫", territorys: [884], guid: "b3a2febd-73ff-44a9-a897-22fa50c74ff3", + version: "0.0.0.4", author: "Veever", note: noteStr)] + +public class the_Grand_Cosmos +{ + const string noteStr = + """ + v0.0.0.4: + 1. 现在支持文字横幅/TTS开关 + 2. 标点开关以及本地开关都在用户设置里面,可自行选择关闭或者开启(默认本地开启) + 3. 有生之年!可能会!添加新的扫帚紫圈的判定方式 + 4. 有生之年!可能会!添加新的Boss2种子的判定方式 + 5. Boss3标记物品水晶灯没标, 目标太大了都能看得见 + 6. 支持副本点位指路,红线为引战范围 + 7. 如果感觉指路点位不够的话请在dc频道说一下或者dc私聊我(24个点位总能够了吧(x)) + 鸭门。 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + [UserSetting("标点开关")] + public bool isMark { get; set; } = true; + + [UserSetting("本地标点开关(打开则为本地开关,关闭则为小队)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + public int DarkShockTTSCount; + public int magicBroomPosCount; + public int Boss2SeedsNotifyCount; + + public bool Boss2WindisWest; + + private readonly object DarkShockTTSLock = new object(); + private readonly object magicBroomPosLock = new object(); + private bool isDRTTS = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + accessory.Method.MarkClear(); + DarkShockTTSCount = 0; + magicBroomPosCount = 0; + Boss2WindisWest = false; + Boss2SeedsNotifyCount = 0; + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + + #region 指路 + [ScriptMethod(name: "指路1组", eventType: EventTypeEnum.Director, eventCondition: ["Command:40000001", "Instance:80030049"])] + public async void Group1(Event @event, ScriptAccessory accessory) + { + await Task.Delay(1000); + DebugMsg("In Group1", accessory); + var pos = new Vector3(0, 0, 329.23f); + List List1 = new List + { + new Vector3(-13.92f, 0.00f, 326.36f), + new Vector3(-74.22f, 0.00f, 320.13f), + new Vector3(-105.53f, 0.05f, 300.47f), + new Vector3(-107.94f, 0.00f, 284.64f), + new Vector3(-101.72f, 0.00f, 274.01f), + new Vector3(-58.48f, 0.00f, 280.89f), + new Vector3(-51.44f, 0.00f, 292.14f), + new Vector3(-23.44f, 0.00f, 285.24f), + new Vector3(-0.30f, 0.00f, 237.67f), + }; + var toPosColor = new Vector4(1.0f, 1.0f, 0.0f, 4.0f); + + FastDp(accessory, "1-1", List1[0], accessory.Data.DefaultSafeColor); + FastDp(accessory, "1-2", List1[1], accessory.Data.DefaultSafeColor); + FastDp(accessory, "1-3", List1[2], toPosColor); + FastDp(accessory, "1-4", List1[3], accessory.Data.DefaultSafeColor); + FastDp(accessory, "1-5", List1[4], accessory.Data.DefaultSafeColor); + FastDp(accessory, "1-6", List1[5], accessory.Data.DefaultSafeColor); + FastDp(accessory, "1-7", List1[6], accessory.Data.DefaultSafeColor); + FastDp(accessory, "1-8", List1[7], accessory.Data.DefaultSafeColor); + FastDp(accessory, "1-9", List1[8], toPosColor); + + } + [ScriptMethod(name: "指路1组删除+指路2组", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:11290"])] + public async void delGroup1Add2(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + await Task.Delay(1000); + var toPosColor = new Vector4(1.0f, 1.0f, 0.0f, 4.0f); + + List List2 = new List + { + new Vector3(13.34f, -7.00f, 115.41f), + new Vector3(64.77f, -13.98f, 35.54f), + new Vector3(-0.40f, -14.00f, -7.85f), + }; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Line1"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 5.0f); + //dp.Color = new Vector4(); + dp.Position = new Vector3(20, -7, 100); + dp.TargetPosition = new Vector3(20, -7, 130); + dp.Scale = new Vector2(6, 30f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "Line2"; + dp1.Color = new Vector4(1.0f, 0.0f, 0.0f, 5.0f); + //dp.Color = new Vector4(); + dp1.Position = new Vector3(92, -14, 23); + dp1.TargetPosition = new Vector3(104, -14, 23); + dp1.Scale = new Vector2(6, 15f); + dp1.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp1); + + FastDp(accessory, "2-1", List2[0], accessory.Data.DefaultSafeColor); + FastDp(accessory, "2-2", List2[1], toPosColor); + FastDp(accessory, "2-2", List2[2], toPosColor); + } + + [ScriptMethod(name: "指路2组删除+指路3组", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:11268"])] + public async void delGroup2Add3(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + await Task.Delay(1000); + var toPosColor = new Vector4(1.0f, 1.0f, 0.0f, 4.0f); + + List List3 = new List + { + new Vector3(-0.14f, -4.00f, -125.28f), + new Vector3(28.01f, -3.99f, -159.18f), + new Vector3(-27.77f, -3.99f, -158.92f), + new Vector3(9.72f, 8.00f, -198.45f), + new Vector3(42.29f, 8.00f, -192.16f), + new Vector3(47.17f, 8.00f, -182.42f), + new Vector3(78.04f, 8.00f, -186.34f), + new Vector3(78.41f, 8.00f, -207.17f), + new Vector3(32.57f, 8.00f, -219.26f), + new Vector3(30.73f, 8.05f, -231.61f), + new Vector3(13.86f, 8.00f, -239.76f), + new Vector3(0.66f, 8.00f, -288.16f), + }; + FastDp(accessory, "3-1", List3[0], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-2", List3[1], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-3", List3[2], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-4", List3[3], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-5", List3[4], toPosColor); + FastDp(accessory, "3-6", List3[5], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-7", List3[6], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-8", List3[7], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-9", List3[8], toPosColor); + FastDp(accessory, "3-10", List3[9], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-11", List3[10], accessory.Data.DefaultSafeColor); + FastDp(accessory, "3-12", List3[11], accessory.Data.DefaultSafeColor); + } + + public void FastDp(ScriptAccessory accessory, string name, Vector3 position, Vector4 color) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color; + //dp.Color = new Vector4(); + dp.Position = position; + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(2); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "删除绘制0", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:1223", "SourcePosition:{\"X\":-0.02,\"Y\":7.98,\"Z\":-355.00}"])] + public async void delDraw0(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "删除绘制1", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:11283"])] + public async void delDraw1(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + #endregion + #region 小怪 + [ScriptMethod(name: "浓云密布", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18720"])] + public void Cloudcover(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "浓云密布"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = @event.EffectPosition(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(6); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "愤怒一击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18721"])] + public void SmiteofRage(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "愤怒一击"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(5f,4f); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "钢铁正义", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18719"])] + public void IronJustice(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "钢铁正义"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(11f); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "自爆", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18725"])] + public void SelfDestruct(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "自爆"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(7); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "忘忧毒液", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18728"])] + public void NepenthicPlunge(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "忘忧毒液"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(10f); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "鼻息风暴", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18729"])] + public void BrewingStorm(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "鼻息风暴"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(5f); + dp.Radian = float.Pi / 180 * 60; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "幻影骑士哈蒙斯救疗眩晕提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18737"]),] + public async void RonkanCureII(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("眩晕哈蒙斯", duration: 4000, true); + accessory.TTS("眩晕哈蒙斯", isTTS, isDRTTS); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Bind1, LocalMark); + await Task.Delay(5000); + accessory.Method.MarkClear(); + } + #endregion + + + #region Boss1 + [ScriptMethod(name: "Boss1死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18281"])] + public void Boss1Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + accessory.TTS("死刑准备", isTTS, isDRTTS); + } + + [ScriptMethod(name: "Boss1AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18851"])] + public void Boss1AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 3000, true); + accessory.TTS("AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "Boss1分摊", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18282"])] + public void Boss1Stack(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Boss1分摊"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 5000; + //dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 5000, true); + accessory.TTS($"与{tname}分摊", isTTS, isDRTTS); + } + + [ScriptMethod(name: "黑暗爆碎", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0060"])] + public void DarkWell(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("全体分散", duration: 5000, true); + accessory.TTS("全体分散", isTTS, isDRTTS); + } + + //var dp = accessory.Data.GetDefaultDrawProperties(); + //dp.Name = $"黑暗爆碎"; + ////dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + //dp.Owner = @event.TargetId(); + //dp.ScaleMode = ScaleMode.ByTime; + //dp.Scale = new Vector2(5); + //dp.DestoryAt = 5000; + //accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "黑暗冲击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18287"])] + public void DarkShock(Event @event, ScriptAccessory accessory) + { + if (DarkShockTTSCount == 10) + { + if (isText) accessory.Method.TextInfo("躲避黄圈", duration: 3000, true); + accessory.TTS("躲避黄圈", isTTS, isDRTTS); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"黑暗冲击{DarkShockTTSCount}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + //dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(6); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + //List TribulationList = new List(); + [ScriptMethod(name: "苦难", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18852"])] + public void Tribulation(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"苦难"; + dp.Color = new Vector4(148 / 255.0f, 0 / 255.0f, 211 / 255.0f, 1.0f); + dp.Position = @event.EffectPosition(); + //dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(3); + dp.DestoryAt = 8500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"苦难2"; + dp1.Color = accessory.Data.DefaultDangerColor; + dp1.Position = @event.EffectPosition(); + //dp1.ScaleMode = ScaleMode.ByTime; + dp1.Scale = new Vector2(6.5f); + dp1.Delay = 13000; + dp1.DestoryAt = 9500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + + Task.Delay(9000); + lock (DarkShockTTSLock) + { + DebugMsg($"DarkShockTTSCount = {DarkShockTTSCount}", accessory); + if (DarkShockTTSCount == 0) + { + if (isText) accessory.Method.TextInfo("集合引导黄圈,随后躲避", duration: 3000, true); + accessory.TTS("集合引导黄圈,随后躲避", isTTS, isDRTTS); + } + DarkShockTTSCount++; + } + + ////DebugMsg($"苦难pos: {@event.EffectPosition()}", accessory); + //List TribulationZAxisList = new List + //{ + // 198.99f, //z[0] from S to N + // 192.98f, //z[1] + // 187f, //z[2] + // 180.99f, //z[3] + // 174.98f //z[4] + //}; + + //DebugMsg($"start check", accessory); + //DebugMsg($"z: {@event.EffectPosition().Z}", accessory); + //if (@event.EffectPosition().Z == TribulationZAxisList[0]) + //{ + // TribulationList[0] = @event.EffectPosition(); + //} + //else if (@event.EffectPosition().Z == TribulationZAxisList[1]) + //{ + // TribulationList[1] = @event.EffectPosition(); + //} + //else if (@event.EffectPosition().Z == TribulationZAxisList[2]) + //{ + // TribulationList[2] = @event.EffectPosition(); + //} + //else if (@event.EffectPosition().Z == TribulationZAxisList[3]) + //{ + // TribulationList[3] = @event.EffectPosition(); + //} + //else if (@event.EffectPosition().Z == TribulationZAxisList[4]) + //{ + // TribulationList[4] = @event.EffectPosition(); + //} + //DebugMsg($"after check", accessory); + //for (int i = 0; i < TribulationList.Count; i++) + //{ + // DebugMsg($"苦难pos[{i}]: {TribulationList[i]}", accessory); + //} + } + + //[ScriptMethod(name: "debug 扫帚pos", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:getpos"])] + //public async void debugPos(Event @event, ScriptAccessory accessory) + //{ + // if (isDebug) + // { + // DebugMsg("geting pos", accessory); + // for(var i = 0; i <= 1; i++) + // { + // await Task.Delay(1000); + // var pos1 = IbcHelper.GetById(0x40001EF3); + // DebugMsg($"{i}pos[0]: {pos1.Position}", accessory); + // } + + // for (var i = 0; i <= 1; i++) + // { + // await Task.Delay(1000); + // var pos1 = IbcHelper.GetById(0x40001EF4); + // DebugMsg($"{i}pos[1]: {pos1.Position}", accessory); + // } + + // for (var i = 0; i <= 1; i++) + // { + // await Task.Delay(1000); + // var pos1 = IbcHelper.GetById(0x40001EF5); + // DebugMsg($"{i}pos[2]: {pos1.Position}", accessory); + // } + + // for (var i = 0; i <= 1; i++) + // { + // await Task.Delay(1000); + // var pos1 = IbcHelper.GetById(0x40001EF6); + // DebugMsg($"{i}pos[3]: {pos1.Position}", accessory); + // } + + // for (var i = 0; i <= 1; i++) + // { + // await Task.Delay(1000); + // var pos1 = IbcHelper.GetById(0x40001EF7); + // DebugMsg($"{i}pos[4]: {pos1.Position}", accessory); + // } + // } + //} + // List BroomList = new List(); + [ScriptMethod(name: "扫帚pos", eventType: EventTypeEnum.SetObjPos, eventCondition: ["Id:003E", "MorlogId:106"])] + public async void magicBroomPos(Event @event, ScriptAccessory accessory) + { + lock (magicBroomPosLock) + { + DebugMsg($"magicBroomPosCount: {magicBroomPosCount}", accessory); + if (magicBroomPosCount <= 4 || (magicBroomPosCount >= 10 && magicBroomPosCount <= 14 )) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扫帚面向"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Scale = new Vector2(1, 5.5f); + dp.DestoryAt = 14500; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"扫帚范围pos"; + //dp1.Color = accessory.Data.DefaultDangerColor; + dp1.Color = new Vector4(255 / 255.0f, 215 / 255.0f, 0 / 255.0f, 1.0f); + dp1.Owner = @event.SourceId(); + dp1.Scale = new Vector2(3f); + dp1.DestoryAt = 14500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + } + magicBroomPosCount++; + } + + + //List TribulationZAxisList = new List + //{ + // 198.99f, //z[0] from S to N + // 192.98f, //z[1] + // 187f, //z[2] + // 180.99f, //z[3] + // 174.98f //z[4] + //}; + //////////// + //List BroomZAxisList = new List + //{ + // 198.23f, //z[0] from S to N + // 192.25f, //z[1] + // 187.03f, //z[2] + // 181.81f, //z[3] + // 175.74f //z[4] + //}; + + //if (@event.SourcePosition().Z == BroomZAxisList[0]) + //{ + + //} + + } + + + + #endregion + + + #region Boss2 + [ScriptMethod(name: "Boss2死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18203"])] + public void Boss2Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + accessory.TTS("死刑准备", isTTS, isDRTTS); + } + + [ScriptMethod(name: "Boss2AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18204"])] + public void Boss2AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 3000, true); + accessory.TTS("AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "Boss2种子搬运提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18206"])] + public void Boss2SeedsNotify(Event @event, ScriptAccessory accessory) + { + if (Boss2SeedsNotifyCount == 0) + { + if (isText) accessory.Method.TextInfo("将种子搬离草地", duration: 8000, true); + accessory.TTS("将种子搬离草地", isTTS, isDRTTS); + } else + { + if (isText) accessory.Method.TextInfo("种子将被击退一格, 将种子搬至安全区", duration: 8000, true); + accessory.TTS("种子将被击退一格, 将种子搬至安全区", isTTS, isDRTTS); + } + + } + + [ScriptMethod(name: "Boss2花雨之歌", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18768"])] + public void OdetoFallenPetals(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("月环,去Boss脚下", duration: 4000, true); + accessory.TTS("月环,去Boss脚下", isTTS, isDRTTS); + + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = $"花雨之歌(单月环)"; + dp2.Color = accessory.Data.DefaultSafeColor; + dp2.Owner = @event.SourceId(); + dp2.Scale = new Vector2(5); + dp2.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + + var dp3 = accessory.Data.GetDefaultDrawProperties(); + dp3.Name = $"花雨之歌指路"; + dp3.Owner = accessory.Data.Me; + dp3.Color = accessory.Data.DefaultSafeColor; + dp3.ScaleMode |= ScaleMode.YByDistance; + dp3.TargetPosition = @event.SourcePosition(); + dp3.Scale = new(2); + dp3.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + } + + [ScriptMethod(name: "强风击退提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18209"])] + public async void IrefulWind(Event @event, ScriptAccessory accessory) + { + var westPos = new Vector3(-21.57f, -12.50f, -60.00f); + //DebugMsg($"SourcePosition: {@event.SourcePosition()}", accessory); + //DebugMsg($"westPos: {westPos}", accessory); + + if (@event.SourcePosition() == westPos) + { + Boss2WindisWest = true; + } + + for (int i = 0; i <= 24; i++) + { + await Task.Delay(500); + var offset = Boss2WindisWest ? -1f : 1f; + foreach (var item in IbcHelper.GetByDataId(accessory, 11269)) + { + DebugMsg($"itemid: {item}", accessory); + //DebugMsg($"itemGameObjectId: {item.GameObjectId}", accessory); + //DebugMsg($"itemOwnerId: {item.OwnerId}", accessory); + //DebugMsg($"itemEntityId: {item.EntityId}", accessory); + //DebugMsg($"itemTargerId: {item.TargetObjectId}", accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "强风种子击退提示"; + dp.Scale = new(1.5f, 10); + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = item.EntityId; + + var targetPosition = item.Position; + targetPosition.X += offset; + DebugMsg($"offset: {offset}", accessory); + dp.TargetPosition = targetPosition; + DebugMsg($"item.Position: {item.Position}", accessory); + DebugMsg($"TargetPosition: {dp.TargetPosition}", accessory); + + dp.Rotation = float.Pi + item.Rotation * float.Pi / 180; + dp.DestoryAt = 500; + + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + + } + #endregion + + + #region Boss3 + [ScriptMethod(name: "左/右炎狱斩", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(1827[45])$"])] + public void Boss3Knout(Event @event, ScriptAccessory accessory) + { + var isR = @event.ActionId() == 18274; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"{(isR ? "左" : "右")}鞭打"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.DestoryAt = 5500; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(40); + dp.Radian = float.Pi / 180 * 180; + dp.Rotation = float.Pi / 180 * 90 * (isR ? -1 : 1); + + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + if (isText) accessory.Method.TextInfo($"去Boss{(isR ? "左" : "右")}面", duration: 4000, true); + accessory.TTS($"去Boss{(isR ? "左" : "右")}面", isTTS, isDRTTS); + } + + [ScriptMethod(name: "火点名", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0019"])] + public async void fireIcon(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo($"注意分散, 远离家具,引导火圈 + 十字", duration: 4000, true); + accessory.TTS($"注意分散, 远离家具,引导火圈加十字", isTTS, isDRTTS); + } + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "火点名圆形"; + dp1.Color = new Vector4(255 / 255.0f, 127 / 255.0f, 80 / 255.0f, 1.0f); + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp1.Owner = @event.TargetId(); + //dp1.ScaleMode = ScaleMode.ByTime; + dp1.Scale = new Vector2(7); + dp1.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + + //// 加入后角色会动,导致一直在旋转 + //await Task.Delay(5000); + //for (int i = 0; i < 4; i++) + //{ + // float rotation = 0; + // switch (i) + // { + // case 0: rotation = float.Pi / 180 * -90; break; + // case 1: rotation = float.Pi / 180 * 90; break; + // case 2: rotation = float.Pi; break; + // case 3: rotation = 0; break; + // } + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = "火点名"; + // dp.Color = new Vector4(255 / 255.0f, 255 / 255.0f, 0 / 255.0f, 1.0f); + // //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + // dp.Owner = @event.TargetId(); + // dp.Scale = new Vector2(4.4f, 10.3f); + // dp.DestoryAt = 10000; + // //dp.Rotation = rotation - @event.TargetRotation(); + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + //} + } + + [ScriptMethod(name: "鬼炎斩", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18268"])] + public void OtherworldlyHeat(Event @event, ScriptAccessory accessory) + { + var posCenter = @event.EffectPosition(); + var posLeftStart = posCenter; + var posLeftEnd = posCenter; + //var posUpStart = posCenter; + //var posUpEnd = posCenter; + + posLeftStart.X += 10.2f / 2; + posLeftStart.X -= 10.2f / 2; + //posUpStart.Z += 10.1f / 2; + //posUpEnd.Z += 10.1f / 2; + + for (var i = 0; i < 4; i++) + { + float rotation = 0; + switch (i) + { + case 0: rotation = float.Pi / 180 * -90; break; + case 1: rotation = float.Pi / 180 * 90; break; + case 2: rotation = float.Pi; break; + case 3: rotation = 0; break; + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "火点名left"; + dp.Color = new Vector4(128 / 255.0f, 0 / 255.0f, 128 / 255.0f, 1.0f); + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = posLeftStart; + dp.TargetPosition = posLeftEnd; + dp.Rotation = rotation; + dp.Scale = new Vector2(4f, 10.3f); + dp.DestoryAt = 3500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + + [ScriptMethod(name: "蓝火传火", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00C3"])] + public async void BluefireIcon(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo($"找家具传火", duration: 4000, true); + accessory.TTS($"找家具传火", isTTS, isDRTTS); + } + + List dataIds = new List { 11278, 11279, 11281, 11280 }; + foreach (var dataId in dataIds) + { + foreach (var item in IbcHelper.GetByDataId(accessory, dataId)) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "蓝火传火"; + dp.Color = new Vector4(0 / 255.0f, 191 / 255.0f, 255 / 255.0f, 1.0f); + //dp.Owner = item.EntityId; + dp.Position = item.Position; + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(2); + dp.DestoryAt = 15000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + } + + [ScriptMethod(name: "炎狱杀点名", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:003[2345]"])] + public async void FiresDomainIcon(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo($"注意远离Boss分散, 将连线拉成紫色, 同时不要将Boss引导到家具", duration: 7500, true); + accessory.TTS($"注意远离Boss分散, 将连线拉成紫色, 同时不要将Boss引导到家具", isTTS, isDRTTS); + } + + //var idToMarkTypeMap = new Dictionary + //{ + // { 0032, KodakkuAssist.Module.GameOperate.MarkType.Attack1 }, + // { 0033, KodakkuAssist.Module.GameOperate.MarkType.Attack2 }, + // { 0034, KodakkuAssist.Module.GameOperate.MarkType.Attack3 }, + // { 0035, KodakkuAssist.Module.GameOperate.MarkType.Attack4 } + //}; + + //if (idToMarkTypeMap.TryGetValue(@event.Id(), out var markType) && isMark) + //{ + // DebugMsg("Marking", accessory); + // accessory.Method.Mark(@event.TargetId(), markType, LocalMark); + //} + DebugMsg($"Start Marking, Id: {@event.Id()}", accessory); + if (@event.Id() == 26) + { + DebugMsg("Start 0032", accessory); + if (isMark) accessory.Method.Mark(@event.TargetId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + } + if (@event.Id() == 27) + { + DebugMsg("Start 0033", accessory); + if (isMark) accessory.Method.Mark(@event.TargetId(), KodakkuAssist.Module.GameOperate.MarkType.Attack2, LocalMark); + } + if (@event.Id() == 28) + { + DebugMsg("Start 0034", accessory); + if (isMark) accessory.Method.Mark(@event.TargetId(), KodakkuAssist.Module.GameOperate.MarkType.Attack3, LocalMark); + } + if (@event.Id() == 29) + { + DebugMsg("Start 0035", accessory); + if (isMark) accessory.Method.Mark(@event.TargetId(), KodakkuAssist.Module.GameOperate.MarkType.Attack4, LocalMark); + } + DebugMsg("End Marking", accessory); + + } + + [ScriptMethod(name: "炎狱杀连线", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0039"])] + public async void FiresDomainTether(Event @event, ScriptAccessory accessory) + { + //if (@event.TargetId() == accessory.Data.Me) + //{ + // if (isText) accessory.Method.TextInfo($"找家具传火", duration: 4000, true); + // accessory.TTS($"找家具传火", isTTS, isDRTTS); + //} + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "蓝火传火"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Owner = item.EntityId; + dp.Owner = @event.SourceId(); + dp.TargetObject= @event.TargetId(); + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Scale = new Vector2(5); + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + // 没必要 + //if (@event.TargetId() == accessory.Data.Me) + //{ + // await Task.Delay(3300); + // if (isText) accessory.Method.TextInfo($"远离顺劈", duration: 4000, true); + // accessory.TTS($"远离顺劈", isTTS, isDRTTS); + //} + } + + [ScriptMethod(name: "炎狱闪", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18273"])] + public async void FiresIre(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "炎狱闪"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Owner = item.EntityId; + dp.Owner = @event.SourceId(); + dp.ScaleMode |= ScaleMode.ByTime; + dp.Scale = new Vector2(20); + dp.DestoryAt = 2000; + dp.Radian = float.Pi / 180 * 90; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Boss3AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18277"])] + public void Boss3AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 5700, true); + accessory.TTS("AOE", isTTS, isDRTTS); + accessory.Method.MarkClear(); + } + + [ScriptMethod(name: "掉落", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18279"])] + public async void fall(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "掉落"; + dp.Color = new Vector4(0 / 255.0f, 191 / 255.0f, 255 / 255.0f, 1.0f); + dp.Position = @event.EffectPosition(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(3); + dp.DestoryAt = 1600; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Boss3死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:18276"])] + public void Boss3Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + accessory.TTS("死刑准备", isTTS, isDRTTS); + accessory.Method.MarkClear(); + } + + #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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + accessory.Method.TTS(text); + } +} + +public static class IbcHelper +{ + public static IBattleChara? GetById(ScriptAccessory accessory, uint id) + { + return (IBattleChara?)accessory.Data.Objects.SearchByEntityId(id); + } + + public static IBattleChara? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me) as IBattleChara; + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } +} + +public static unsafe class IBattleCharaExtensions +{ + public static bool HasStatus(this IBattleChara ibc, uint statusId, float remaining = -1) + { + return ibc.StatusList.Any(s => s.StatusId == statusId && s.RemainingTime > remaining); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } +} + diff --git a/VeeverSW/05-Shadowbringers/the-Qitana-Ravel.cs b/VeeverSW/05-Shadowbringers/the-Qitana-Ravel.cs new file mode 100644 index 0000000..217a6d0 --- /dev/null +++ b/VeeverSW/05-Shadowbringers/the-Qitana-Ravel.cs @@ -0,0 +1,949 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.UI; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using Lumina.Data.Parsing.Layer; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Net; +using System.Numerics; +using System.Reflection.Metadata; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.Arm; +using System.Threading.Tasks; +using System.Windows; +using System.Xml.Linq; +using static Lumina.Data.Parsing.Layer.LayerCommon; + +namespace Veever.Shadowbringers.the_Qitana_Ravel; + +[ScriptType(name: "LV.75 文明古迹奇坦那神影洞", territorys: [823], guid: "50c922c1-1ecd-4750-8b55-24f19793408f", + version: "0.0.0.3", author: "Veever", note: noteStr)] + +public class the_Qitana_Ravel +{ + const string noteStr = + """ + v0.0.0.3: + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. 只用了单回放进行测试,不能保证绘图完全准确 + 鸭门。 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = true; + + //[UserSetting("指路开关")] + //public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队)")] + //public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + //private readonly object OminousWindMarkerTTSLock = new object(); + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] + public void debug(Event @event, ScriptAccessory accessory) + { + + } + + #region 雕像守卫 + //守卫1: 右上左下 石头 + //守卫2: 左上右下 石头 + + // guard1 + // right0: 620.22 + // right1: 627.75 + // right2: 635.25 + // right3: 642.75 + // right4: 650.38 + // right5: 657.87 + + + // guard2 + + + + [ScriptMethod(name: "雕像激活", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0025"])] + public void StatueActivate(Event @event, ScriptAccessory accessory) + { + var sourcePos = @event.SourcePosition(); + //DrawHelper.DrawCircle(accessory, sourcePos, new Vector2(3f), 6000, $"StatueActivate-{@event.SourceId()}", accessory.Data.DefaultSafeColor); + + var aoeType = GetAOETypeByPosition(sourcePos); + + DebugMsg($"雕像激活 - {aoeType}- {sourcePos}", accessory); + + DrawWrathAOE(accessory, sourcePos, aoeType, @event.SourceId(), @event); + } + + private string GetAOETypeByPosition(Vector3 position) + { + var x = position.X; + var z = position.Z; + + if ((Math.Abs(x - (-17f)) < 1f && Math.Abs(z - 627f) < 1f) || + (Math.Abs(x - 17f) < 1f && Math.Abs(z - 642f) < 1f) || + (Math.Abs(x - (-17f)) < 1f && Math.Abs(z - 436f) < 1f) || + (Math.Abs(x - 17f) < 1f && Math.Abs(z - 421f) < 1f)) + { + return "mid"; + } + + if ((Math.Abs(x - (-17f)) < 1f && Math.Abs(z - 642f) < 1f) || + (Math.Abs(x - 17f) < 1f && Math.Abs(z - 627f) < 1f) || + (Math.Abs(x - 17f) < 1f && Math.Abs(z - 436f) < 1f) || + (Math.Abs(x - (-17f)) < 1f && Math.Abs(z - 421f) < 1f)) + { + return "short"; + } + + return "long"; + } + + private void DrawWrathAOE(ScriptAccessory accessory, Vector3 position, string aoeType, ulong sourceId, Event @event) + { + switch (aoeType) + { + case "short": + WrathOfTheRonkaShort(@event, accessory); + break; + case "mid": + WrathOfTheRonkaMedium(@event, accessory); + break; + default: + WrathOfTheRonkaLong(@event, accessory); + break; + } + //position.Z += 4.4f; + //position.X = 17.40f; + //DrawHelper.DrawRect(accessory, position, position + new Vector3(scale.X, 0, 0), scale, 6000, name, accessory.Data.DefaultDangerColor, rotation: float.Pi / 2); + } + + //下面三个condition不触发,只是留个名字懒得删了(x),应该不会有人看这个屎山代码 + [ScriptMethod(name: "罗肯之怒长距离", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15918"], userControl: false)] + public void WrathOfTheRonkaLong(Event @event, ScriptAccessory accessory) + { + var pos = @event.SourcePosition(); + var x = pos.X; + var z = pos.Z; + var rot = float.Pi; + if (((Math.Abs(z) < 660f && Math.Abs(z) > 612f) && (x > -23f && x < -15f)) || ((Math.Abs(z) < 460f && Math.Abs(z) > 400f) && (x > -23f && x < -15f))) + { + DebugMsg($"rot changed", accessory); + rot = 0; + } + DebugMsg($"rot = {rot}", accessory); + DrawHelper.DrawRect(accessory, pos, pos + new Vector3(35f, 0, 0), new Vector2(8f, 35f), 6000, $"WrathLong-{@event.SourceId()}", accessory.Data.DefaultDangerColor, rotation: rot); + } + + [ScriptMethod(name: "罗肯之怒中距离", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15917"], userControl: false)] + public void WrathOfTheRonkaMedium(Event @event, ScriptAccessory accessory) + { + var pos = @event.SourcePosition(); + var x = pos.X; + var z = pos.Z; + var rot = float.Pi; + if (((Math.Abs(z) < 660f && Math.Abs(z) > 612f) && (x > -23f && x < -15f)) || ((Math.Abs(z) < 460f && Math.Abs(z) > 400f) && (x > -23f && x < -15f))) + { + DebugMsg($"rot changed", accessory); + rot = 0; + } + DebugMsg($"rot = {rot}", accessory); + DrawHelper.DrawRect(accessory, pos, pos + new Vector3(22f, 0, 0), new Vector2(8f, 22f), 6000, $"WrathMedium-{@event.SourceId()}", accessory.Data.DefaultDangerColor, rotation: rot); + } + + [ScriptMethod(name: "罗肯之怒短距离", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15916"], userControl: false)] + public void WrathOfTheRonkaShort(Event @event, ScriptAccessory accessory) + { + var pos = @event.SourcePosition(); + var x = pos.X; + var z = pos.Z; + var rot = float.Pi; + if (((Math.Abs(z) < 660f && Math.Abs(z) > 612f) && (x > -23f && x < -15f)) || ((Math.Abs(z) < 460f && Math.Abs(z) > 400f) && (x > -23f && x < -15f))) + { + DebugMsg($"rot changed", accessory); + rot = 0; + } + DebugMsg($"rot = {rot}", accessory); + DrawHelper.DrawRect(accessory, pos, pos + new Vector3(12f, 0, 0), new Vector2(8f, 12f), 6000, $"WrathShort-{@event.SourceId()}", accessory.Data.DefaultDangerColor, rotation: rot); + } + + [ScriptMethod(name: "罗肯之怒清除", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:10816"], userControl: false)] + public void WrathOfTheRonkaClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw("WrathLong-.*|WrathMedium-.*|WrathShort-.*"); + } + + #endregion + + + #region 小怪 + [ScriptMethod(name: "隆卡深渊", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:17387"])] + public void RonkanAbyss(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("注意远离", duration: 2700, true); + if (isTTS) accessory.Method.TTS("注意远离"); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(6f), 2700, $"RonkanAbyss-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "隆卡深渊清除", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:17387"], userControl: false)] + public void RonkanAbyssClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"RonkanAbyss-{@event.SourceId()}"); + } + + + [ScriptMethod(name: "高热激光", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15923"])] + public void BurningBeam(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawRectOwner(accessory, @event.SourceId(), new Vector2(4, 15f), 2700, $"BurningBeam-{@event.SourceId()}", accessory.Data.DefaultDangerColor, shape: ScaleMode.ByTime); + } + + [ScriptMethod(name: "高热激光清除", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:15923"], userControl: false)] + public void BurningBeamClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"BurningBeam-{@event.SourceId()}"); + } + + [ScriptMethod(name: "甩头攻击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15924"])] + public void HoodSwing(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawFanOwner(accessory, @event.SourceId(), null, new Vector2(9f), 120f, 2700, $"HoodSwing-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "甩头攻击清除", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:15924"], userControl: false)] + public void HoodSwingClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"BurningBeam-{@event.SourceId()}"); + } + + [ScriptMethod(name: "吐出罪孽", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15926"])] + public void Sinspitter(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(3f), 2700, $"Sinspitter-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "吐出罪孽清除", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:15926"], userControl: false)] + public void SinspitterClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Sinspitter-{@event.SourceId()}"); + } + + [ScriptMethod(name: "自爆", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:16260"])] + public void Selfdestruct(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(6.9f), 2700, $"Selfdestruct-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "自爆清除", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:16260"], userControl: false)] + public void SelfdestructClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Selfdestruct-{@event.SourceId()}"); + } + + #endregion + + + #region Boss1 + [ScriptMethod(name: "石拳", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15497"])] + public void Stonefist(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑点名", duration: 3700, true); + if (isTTS) accessory.Method.TTS("死刑点名"); + } + + [ScriptMethod(name: "投射石块", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15498"])] + public void SunToss(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("注意远离", duration: 3700, true); + if (isTTS) accessory.Method.TTS("注意远离"); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(5f), 2700, $"SunToss-{@event.SourceId()}", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "AOE预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15499"])] + public void AOENotify(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 2700, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + + [ScriptMethod(name: "墙壁绘图", eventType: EventTypeEnum.ObjectEffect, eventCondition: ["Id2:8", "Id1:4"])] + public void DrawWall(Event @event, ScriptAccessory accessory) + { + DebugMsg("Calling Draw Wall", accessory); + var pos = @event.SourcePosition(); + var left = new Vector3(-10f, 5.15f, 286.91f); + var right = new Vector3(10f, 5.15f, 286.91f); + var originpos1 = new Vector3(-7.22f, 5.35f, 328.78f); + var originpos2 = new Vector3(8.01f, 5.33f, 328.78f); + if (@event.SourcePosition() != originpos1 && @event.SourcePosition() != originpos2) + { + DebugMsg($"Wall position not match, pos = {pos}", accessory); + return; + } + var source = @event.SourcePosition(); + if (pos.X == -7.22f) + { + source = left; + pos.X = left.X; + } else + { + source = right; + pos.X = right.X; + } + + DrawHelper.DrawRect(accessory, source, pos, new Vector2(20f, 60f), 10000, $"DrawWall"); + } + + [ScriptMethod(name: "洛查特尔的愤怒-Right", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15503"])] + public void LozatlsFuryRight(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawFanOwner(accessory, @event.SourceId(), (-float.Pi / 2), new Vector2(20f), 180f, 3700, $"LozatlsFuryRight"); + } + + [ScriptMethod(name: "洛查特尔的愤怒-Left", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15504"])] + public void LozatlsFuryLeft(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawFanOwner(accessory, @event.SourceId(), (float.Pi / 2), new Vector2(20f), 180f, 3700, $"LozatlsFuryLeft"); + } + + + + + + + #endregion + + + + #region Boss2 + [ScriptMethod(name: "裂肉尖牙", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15505"])] + public void RipperFang(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑点名", duration: 3700, true); + if (isTTS) accessory.Method.TTS("死刑点名"); + } + + + [ScriptMethod(name: "AOE预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(15506|15507)$"])] + public async void AOENotifyBoss2(Event @event, ScriptAccessory accessory) + { + if (@event.ActionId() == 15507) + { + if (isText) accessory.Method.TextInfo("多段AOE", duration: 2700, true); + if (isTTS) accessory.Method.TTS("多段AOE"); + } else + { + if (isText) accessory.Method.TextInfo("AOE", duration: 2700, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + await Task.Delay(3000); + accessory.Method.RemoveDraw($"BurningBeam-.*"); + + } + + [ScriptMethod(name: "落石", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(15509|15510|15511)$"])] + public async void StoneFall(Event @event, ScriptAccessory accessory) + { + DebugMsg("rock", accessory); + if (@event.ActionId() == 15509) + { + DebugMsg("small rock", accessory); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(2f), 1700, $"落石pos-{@event.EffectPosition()}"); + } + else if(@event.ActionId() == 15511) + { + DebugMsg("large rock", accessory); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(4f), 1700, $"落石pos-{@event.EffectPosition()}"); + + await Task.Delay(2000); + var stoenList = IbcHelper.GetByDataId(accessory, 2009805); + foreach (var stone in stoenList) + { + if (stone == null || !stone.IsValid()) continue; + DrawHelper.DrawFanOwner(accessory, stone.GameObjectId, null, new Vector2(15f), 30f, 15000, $"落石掉落-{stone.GameObjectId}", color: new Vector4(1, 1, 0, 1), scaleByTime: false); + } + } else + { + DebugMsg("mid rock", accessory); + DrawHelper.DrawCircle(accessory, @event.EffectPosition(), new Vector2(3f), 1700, $"落石pos-{@event.EffectPosition()}"); + } + } + + + #endregion + + + #region Boss3 + [ScriptMethod(name: "撕碎", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15513"])] + public void Rend(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑点名", duration: 3700, true); + if (isTTS) accessory.Method.TTS("死刑点名"); + } + + [ScriptMethod(name: "蓄力冲撞提示", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0039"])] + public void HoundoutofHeaven(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离Boss,拉长连线至紫色", duration: 4700, true); + if (isTTS) accessory.Method.TTS("远离Boss,拉长连线至紫色"); + } + + [ScriptMethod(name: "AOE预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15515"])] + public void AOENotifyBoss3(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 2700, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "吸引", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:17168"])] + public void Inhale(Event @event, ScriptAccessory accessory) + { + + DrawHelper.DrawDisplacement(accessory, @event.EffectPosition(), new Vector2(2f), 4000, $"吸引"); + } + + [ScriptMethod(name: "吐息", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15520"])] + public void HeavingBreath(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "吐息"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = accessory.Data.Me; + dp.Scale = new Vector2(2, 35); + dp.Rotation = 0; + dp.DestoryAt = 3200; + dp.FixRotation = true; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + if (isText) accessory.Method.TextInfo("躲避毒圈击退", duration: 4700, true); + if (isTTS) accessory.Method.TTS("躲避毒圈击退"); + } + + [ScriptMethod(name: "信仰宣言中间", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15522"])] + public void ConfessionofFaith(Event @event, ScriptAccessory accessory) + { + var pos = @event.SourcePosition(); + pos.Z -= 4f; + DrawHelper.DrawFan(accessory, pos, null, new Vector2(60f), 60f, 5200, $"信仰宣言", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "信仰宣言点名分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15523"])] + public void ConfessionofFaithMark(Event @event, ScriptAccessory accessory) + { + DrawHelper.DrawCircleObject(accessory, @event.TargetId(), new Vector2(5f), 5500, $"信仰宣言点名", new Vector4(1, 1, 0, 1)); + } + + [ScriptMethod(name: "信仰宣言两侧", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15524"])] + public void ConfessionofFaithTwoSides(Event @event, ScriptAccessory accessory) + { + var pos = @event.SourcePosition(); + pos.Z -= 7f; + DrawHelper.DrawFan(accessory, pos, 40 * (float.Pi / 180) , new Vector2(60f), 60f, 5500, $"信仰宣言", accessory.Data.DefaultDangerColor); + DrawHelper.DrawFan(accessory, pos, -40 * (float.Pi / 180), new Vector2(60f), 60f, 5500, $"信仰宣言", accessory.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "信仰宣言点名分摊", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:15525"])] + public void ConfessionofFaithMarkStack(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("中间分摊", duration: 4700, true); + if (isTTS) accessory.Method.TTS("中间分摊"); + DrawHelper.DrawCircleObject(accessory, @event.TargetId(), new Vector2(5f), 5500, $"信仰宣言分摊", accessory.Data.DefaultSafeColor); + } + + #endregion + + + + + #region Helpers + + public unsafe static float GetStatusRemainingTime(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); + } + } + + + private unsafe uint[] ScanTether(Event evt, ScriptAccessory sa, uint id) + { + if (sa?.Data?.Objects == null) return Array.Empty(); + List dataId = [id]; + List players = []; + foreach (var fire in sa.Data.Objects.Where(x => dataId.Contains(x.DataId))) + { + if (fire?.Address == null) continue; + var targetId = ((BattleChara*)fire.Address)->Vfx.Tethers[0].TargetId.ObjectId; + players.Add(targetId); + } + DebugMsg($"players: {string.Join(", ", players)}", sa); + return players.ToArray(); + } + + public static class DrawHelper + { + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, float rotation = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectOwner(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, float rotation = 0, ScaleMode shape = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = shape; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float ?rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanOwner(ScriptAccessory accessory, ulong owner, float ?rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawArrowOwner(ScriptAccessory accessory, ulong ob, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + +} + + +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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + +public static class IbcHelper +{ + public static KodakkuAssist.Data.IGameObject? GetById(ScriptAccessory accessory, uint id) + { + return accessory.Data.Objects.SearchByEntityId(id); + } + + public static KodakkuAssist.Data.IGameObject? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me); + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } + + public static IEnumerable GetParty(ScriptAccessory accessory) + { + foreach (var pid in accessory.Data.PartyList) + { + var obj = accessory.Data.Objects.SearchByEntityId(pid); + if (obj != null) yield return obj; + } + } + + public static IEnumerable GetPartyEntities(ScriptAccessory accessory) + { + return accessory.Data.Objects.Where(obj => accessory.Data.PartyList.Contains(obj.EntityId)); + } + + public static bool HasStatus(this IBattleChara ibc, uint statusId) + { + return ibc.StatusList.Any(x => x.StatusId == statusId); + } + + public static bool HasStatusAny(this IBattleChara ibc, uint[] statusIds) + { + return ibc.StatusList.Any(x => statusIds.Contains(x.StatusId)); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } +} + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + //accessory.Method.SendChat($"/e 向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } + #endregion +} diff --git a/VeeverSW/05-Shadowbringers/the-Seat-of-Sacrifice.cs b/VeeverSW/05-Shadowbringers/the-Seat-of-Sacrifice.cs new file mode 100644 index 0000000..5b22c01 --- /dev/null +++ b/VeeverSW/05-Shadowbringers/the-Seat-of-Sacrifice.cs @@ -0,0 +1,2090 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Common.Lua; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Xml.Linq; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.Shadowbringers.theSeatofSacrifice; + +[ScriptType(name: "LV.80 光之战士歼灭战 [the Seat of Sacrifice]", territorys: [922], guid: "864c6d7e-20bd-49b6-93ec-31b4d70e1afd", + version: "0.0.0.9", author: "Veever", note: noteStr)] + +public class theSeatofSacrifice +{ + const string noteStr = + """ + v0.0.0.9: + 1. 现已支持几乎所有机制播报及绘图 + 2. 支持DR 自动动态演练开关(默认为打开状态) + 3. 目前光明剑不确定是否只有两个情况,如果有更多的情况没有画出来的话请带arr回放在dc向我反馈 + 4. 现在支持文字横幅/TTS开关 + 5. v0.0.0.3,删除自动动态演练开关 关闭后会offload的方法 + 6. 更新光之剑出现在东西侧的情况 + 7. v0.0.0.4, 可能解决了光之剑东西侧画反的问题(回放太少所以不清楚是否解决,如果还是画错请dc@我) + 鸭门。 + """; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + [UserSetting("DR 自动动态演练开关(Auto QTE Trigger(Daily Routines))")] + public bool isDRQTE { get; set; } = true; + + public enum Language + { + Chinese, + English + } + + private readonly object RadiantMeteorTTSLock = new object(); + private readonly object RadiantDesperadoLock = new object(); + private readonly object FlareBreathLock = new object(); + private readonly object RadiantBraverLock = new object(); + + public bool fireRecord; + public bool iceRecord; + public string DelugeofDeathName; + public int RadiantMeteorTTS; + public bool To00A1TTS; + public int RadiantDesperadoTTS; + public int FlareBreathTTS; + public int RadiantBraverTTS; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + fireRecord = false; + iceRecord = false; + DelugeofDeathName = "未知目标"; + RadiantMeteorTTS = 0; + To00A1TTS = false; + RadiantDesperadoTTS = 0; + FlareBreathTTS = 0; + RadiantBraverTTS = 0; + } + + [ScriptMethod(name: "恐惧释放", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20263"])] + public void TerrorUnleashed(Event @event, ScriptAccessory accessory) + { + string msg = language == Language.Chinese ? "全员1血,奶妈注意回复至满血" : "Everyone at 1 HP, healers top everyone off"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "绝对爆炎", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00E3"])] + public async void AbsoluteFireIII(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId == accessory.Data.Me) + { + string msg = language == Language.Chinese ? "准备停止移动" : "Be ready to STOP"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 2000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + await Task.Delay(4700); + string msg1 = language == Language.Chinese ? "停止移动" : "STOP"; + if (isText) accessory.Method.TextInfo($"{msg1}", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"{msg1}"); + } + } + + [ScriptMethod(name: "绝对冰封", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00E1"])] + public async void AbsoluteBlizzardIII(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId == accessory.Data.Me) + { + string msg = language == Language.Chinese ? "准备持续移动" : "Get ready to keep moving"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 2000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + + await Task.Delay(4700); + + string msg1 = language == Language.Chinese ? "持续移动,不要停" : "Keep moving"; + if (isText) accessory.Method.TextInfo($"{msg1}", duration: 2500, true); + if (isTTS) accessory.Method.TTS($"{msg1}"); + } + } + + [ScriptMethod(name: "魔法剑·绝对爆炎记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20242"])] + public async void ImbuedAbsoluteFireIII(Event @event, ScriptAccessory accessory) + { + string msg = language == Language.Chinese ? "一会儿准备停止移动" : "Get ready to stop moving"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + fireRecord = true; + } + + [ScriptMethod(name: "魔法剑·绝对冰封记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20243"])] + public async void ImbuedAbsoluteBlizzardIII(Event @event, ScriptAccessory accessory) + { + string msg = language == Language.Chinese ? "一会儿准备持续移动" : "Get ready to keep moving"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + iceRecord = true; + } + + [ScriptMethod(name: "光明利剑(钢铁)&魔法剑技·光明利剑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(20240|20299)$"])] + public async void CoruscantSaberOutside(Event @event, ScriptAccessory accessory) + { + if (@event.ActionId == 20299) + { + string msg = language == Language.Chinese ? $"钢铁,远离Boss,准备{(fireRecord ? "停止移动" : "持续移动")}" : + $"Chariot — move away from boss, get ready to {(fireRecord ? "stop moving" : "keep moving")}."; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 5500, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? "钢铁,远离Boss" : "Chariot — move away from boss"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 6700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "光明利剑(钢铁)&魔法剑技·光明利剑"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId; + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(10); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (fireRecord) + { + await Task.Delay(7000); + string msg = language == Language.Chinese ? "停止移动" : "stop moving"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + fireRecord = false; + } + if (iceRecord) + { + await Task.Delay(7000); + string msg = language == Language.Chinese ? "持续移动" : "keep moving"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 2500, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + iceRecord = false; + } + } + [ScriptMethod(name: "光明利剑(月环)&魔法剑技·光明利剑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(20241|20300)$"])] + public async void ImbuedCoruscance(Event @event, ScriptAccessory accessory) + { + if (@event.ActionId == 20300) + { + string msg = language == Language.Chinese ? $"月环,去Boss脚下,准备{(fireRecord ? "停止移动" : "持续移动")}" : + $"Dynamo — go under the boss, get ready to {(fireRecord ? "stop moving" : "keep moving")}." +; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 5500, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } else + { + string msg = language == Language.Chinese ? "月环,去Boss脚下" : "Dynamo — go under the boss"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 6500, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "光明利剑(月环)&魔法剑技·光明利剑"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId; + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(5); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "月环指路"; + dp1.Owner = accessory.Data.Me; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.ScaleMode |= ScaleMode.YByDistance; + dp1.TargetPosition = @event.SourcePosition; + dp1.Scale = new(2); + dp1.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + + if (fireRecord) + { + await Task.Delay(7000); + string msg = language == Language.Chinese ? "停止移动" : "stop moving"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 2500, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + fireRecord = false; + } + if (iceRecord) + { + await Task.Delay(7000); + string msg = language == Language.Chinese ? "一会儿准备持续移动" : "Prepare keep moving"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 2500, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + iceRecord = false; + } + } + + [ScriptMethod(name: "光之剑", eventType: EventTypeEnum.EnvControl, eventCondition: ["Id:01000800"])] + public void SwordofLight(Event @event, ScriptAccessory accessory) + { + if(@event.Index() == 0x14) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "光之剑南北1"; + dp.Color = new Vector4(253 / 255.0f, 223 / 255.0f, 196 / 255.0f, 1.0f); + dp.Position = new Vector3(100.00f, 0.00f, 80.00f); + dp.TargetPosition = new Vector3(100.00f, 0.00f, 100.00f); + dp.Radian = float.Pi / 180 * 53; + dp.Scale = new(60); + dp.DestoryAt = 11000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + if (@event.Index() == 0x16) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "光之剑南北2"; + dp.Color = new Vector4(253 / 255.0f, 223 / 255.0f, 196 / 255.0f, 1.0f); + dp.Position = new Vector3(100.00f, 0.00f, 120.00f); + dp.TargetPosition = new Vector3(100.00f, 0.00f, 100.00f); + dp.Radian = float.Pi / 180 * 53; + dp.Scale = new(60); + dp.DestoryAt = 11000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + if (@event.Index() == 0x15) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "光之剑东西1"; + dp.Color = new Vector4(253 / 255.0f, 223 / 255.0f, 196 / 255.0f, 1.0f); + + dp.Position = new Vector3(120.00f, 0.00f, 100.00f); + dp.TargetPosition = new Vector3(100.00f, 0.00f, 100.00f); + dp.Radian = float.Pi / 180 * 53; + dp.Scale = new(60); + dp.DestoryAt = 11000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + if (@event.Index() == 0x17) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "光之剑东西2"; + dp.Color = new Vector4(253 / 255.0f, 223 / 255.0f, 196 / 255.0f, 1.0f); + dp.Position = new Vector3(80.00f, 0.00f, 100.00f); + dp.TargetPosition = new Vector3(100.00f, 0.00f, 100.00f); + dp.Radian = float.Pi / 180 * 53; + dp.Scale = new(60); + dp.DestoryAt = 11000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + } + + [ScriptMethod(name: "俯冲", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20261"])] + public void Cauterize(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "俯冲"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId; + dp.Scale = new Vector2(20, 40); + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "生辰星位", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:21297"])] + public void Ascendance(Event @event, ScriptAccessory accessory) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + if (isDRQTE) accessory.Method.SendChat("/pdr load autoQTE"); + } + + [ScriptMethod(name: "究极·交汇", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:21627"])] + public async void UltimateCrossover(Event @event, ScriptAccessory accessory) + { + await Task.Delay(3400); + string msg = language == Language.Chinese ? "坦克LB" : "Tank LB"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 6000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "踩塔提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20284"])] + public async void SpecterofLight(Event @event, ScriptAccessory accessory) + { + await Task.Delay(10000); + string msg = language == Language.Chinese ? "踩塔,每个塔至少两人" : "Soak the towers, two each"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 6000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + [ScriptMethod(name: "忍者击退提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20252"])] + public async void SuitonSan(Event @event, ScriptAccessory accessory) + { + string msg = language == Language.Chinese ? "击退" : "Knockback"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"忍者击退提示"; + dp1.Scale = new(1.5f, 26); + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.Owner = accessory.Data.Me; + dp1.TargetPosition = @event.TargetPosition; + dp1.Rotation = float.Pi; + dp1.DestoryAt = 5700; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + + [ScriptMethod(name: "远古龙炎冲", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20265"])] + public void ElddragonDive(Event @event, ScriptAccessory accessory) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "狱火大地", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20254"])] + public void BrimstoneEarth(Event @event, ScriptAccessory accessory) + { + //accessory.Method.TextInfo("AOE", duration: 4700, true); + //accessory.TTS("AOE", isTTS, isDRTTS); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "狱火大地"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition; + dp.ScaleMode = ScaleMode.ByTime; + dp.Delay = 8000; + dp.Scale = new Vector2(15); + dp.DestoryAt = 14000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + + [ScriptMethod(name: "核爆", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20256"])] + public void DelugeofDeath(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + DelugeofDeathName = tname; + } + + [ScriptMethod(name: "分摊", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00A1"])] + public async void To00A1(Event @event, ScriptAccessory accessory) + { + await Task.Delay(350); + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + if (To00A1TTS == false) + { + string msg = language == Language.Chinese ? $"与{tname}分摊, 核爆{DelugeofDeathName}远离" : $"Stack with {tname}, avoid {DelugeofDeathName} flare"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } else + { + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + To00A1TTS = true; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "分摊"; + dp.Color = new Vector4(0 / 255.0f, 255 / 255.0f, 255 / 255.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId; + dp.Scale = new Vector2(6); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20264"])] + public void TheBitterEnd(Event @event, ScriptAccessory accessory) + { + string msg = language == Language.Chinese ? "死刑准备" : "Tankbuster"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "LB1顺劈", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00EA"])] + public async void RadiantBraver(Event @event, ScriptAccessory accessory) + { + lock(RadiantBraverLock) + { + if (RadiantBraverTTS == 0) + { + string msg = language == Language.Chinese ? "扇形顺劈, 不要重叠" : "Conal cleaves, don’t stack"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + RadiantBraverTTS++; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "LB1顺劈"; + dp.Color = new Vector4(253 / 255.0f, 223 / 255.0f, 196 / 255.0f, 1.0f); + dp.Position = new Vector3(100.00f, 0.00f, 100.00f); + dp.TargetObject = @event.TargetId; + dp.Radian = float.Pi / 180 * 90; + dp.Scale = new(60); + dp.DestoryAt = 9000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + + [ScriptMethod(name: "LB2分组分摊", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:20294"])] + public async void RadiantDesperado(Event @event, ScriptAccessory accessory) + { + lock(RadiantDesperadoLock) + { + if (RadiantDesperadoTTS == 0) + { + string msg = language == Language.Chinese ? "分组44分摊,不要重叠" : "Group stacks, 4 each side"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 8000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + RadiantDesperadoTTS++; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "RadiantDesperado"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId; + dp.TargetObject = @event.TargetId; + dp.Scale = new Vector2(6, 20); + dp.DestoryAt = 10000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + + [ScriptMethod(name: "LB3陨石点名提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:20251"])] + public async void RadiantMeteor(Event @event, ScriptAccessory accessory) + { + lock(RadiantMeteorTTSLock) + { + if (RadiantMeteorTTS == 0) + { + string msg = language == Language.Chinese ? "陨石点名, 让出中间位置 非点名玩家去中间" : "Meteors out, rest in"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 8000, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + } + RadiantMeteorTTS++; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "LB3陨石点名提示"; + dp.Color = new Vector4(138 / 255.0f, 43 / 255.0f, 251 / 226.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId; + dp.Scale = new Vector2(20); + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "核爆吐息(连线处理)", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0011"])] + public async void FlareBreath(Event @event, ScriptAccessory accessory) + { + await Task.Delay(1000); + lock(FlareBreathLock) + { + List vectorList = new List + { + new Vector3(83.71f, 0.00f, 83.80f), + new Vector3(115.75f, 0.00f, 84.96f), + new Vector3(116.72f, 0.00f, 116.73f), + new Vector3(84.40f, 0.00f, 115.74f), + }; + if (FlareBreathTTS == 0) + { + string msg = language == Language.Chinese ? "将线拉到对应的小怪的角落安全范围" : "Take your tether to your add’s corner"; + if (isText) accessory.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"{msg}"); + for (int i = 0; i < 4; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"核爆吐息安全点{i}"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Position = vectorList[i]; + dp.Scale = new Vector2(4); + dp.DestoryAt = 9000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + FlareBreathTTS++; + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "核爆吐息(连线处理)"; + dp1.Color = new Vector4(253 / 255.0f, 223 / 255.0f, 196 / 255.0f, 1.0f); + dp1.Owner = @event.SourceId; + dp1.TargetObject = @event.TargetId; + dp1.Radian = float.Pi / 180 * 90; + dp1.Scale = new(60); + dp1.DestoryAt = 9000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp1); + + if (@event.TargetId == accessory.Data.Me) + { + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = "核爆吐息指路"; + dp2.Owner = accessory.Data.Me; + dp2.Color = accessory.Data.DefaultSafeColor; + dp2.ScaleMode |= ScaleMode.YByDistance; + dp2.TargetPosition = @event.SourcePosition; + dp2.Scale = new(2); + dp2.DestoryAt = 9000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + } + } + } +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战) / "Ranged DPS"(远程) / "Unknown" / "None" + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + return playerObject.ClassJob.Value.Role switch + { + 1 => "Tank", // 坦克 + 4 => "Healer", // 治疗 + 2 => "Melee DPS", // 近战DPS + 3 => "Ranged DPS", // 远程DPS + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + public static string GetObjectName(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return "None"; + return obj.Name.ToString(); + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } + + [Flags] + public enum DrawState : uint + { + Invisibility = 0x00_00_00_02, + IsLoading = 0x00_00_08_00, + SomeNpcFlag = 0x00_00_01_00, + MaybeCulled = 0x00_00_04_00, + MaybeHiddenMinion = 0x00_00_80_00, + MaybeHiddenSummon = 0x00_80_00_00, + } + + public static unsafe DrawState* ActorDrawState(IGameObject actor) + => (DrawState*)(&((GameObject*)actor.Address)->RenderFlags); + + /// + /// 检查对象可见性(Read) + /// + /// ScriptAccessory + /// Obj need check + /// true=检查可见,false=检查不可见 + /// 如果符合检查条件返回 True + public static unsafe bool IsActorVisible(this ScriptAccessory sa, IGameObject? obj, bool checkVisible = true) + { + if (obj == null) return false; + + try + { + var state = *ActorDrawState(obj); + bool isVisible = (state & DrawState.Invisibility) == 0; + + return checkVisible ? isVisible : !isVisible; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } + + /// + /// 设置对象可见性(Write) + /// + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + if (actor == null) return; + + try + { + var statePtr = ActorDrawState(actor); + if (visible) + *statePtr &= ~DrawState.Invisibility; + else + *statePtr |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + throw; + } + } + + /// + /// Check DrawState + /// + public static unsafe bool HasDrawState(this ScriptAccessory sa, IGameObject? actor, DrawState state) + { + if (actor == null) return false; + + try + { + return (*ActorDrawState(actor) & state) != 0; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float? rotation = null, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosTarget(ScriptAccessory accessory, Vector3 pos, Vector3 targetpos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.TargetPosition = targetpos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 diff --git a/VeeverSW/06-EndWalker/the-Dead-Ends.cs b/VeeverSW/06-EndWalker/the-Dead-Ends.cs new file mode 100644 index 0000000..2f3b6cf --- /dev/null +++ b/VeeverSW/06-EndWalker/the-Dead-Ends.cs @@ -0,0 +1,1813 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using static FFXIVClientStructs.FFXIV.Client.Graphics.Render.Skeleton; + +namespace Veever.EndWalker.the_Dead_Ends; + +[ScriptType(name: Name, territorys: [973], guid: "850c08df-adad-43f7-8a2a-f8da31df4e84", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + + +public class the_Dead_Ends +{ + const string NoteStr = + """ + v0.0.0.3 + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. Boss2击退距离因为缺乏实例所以不确定,如果有未使用防击退的arr可以给我发一份 + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + 2. Boss 2 knockback distance is uncertain due to lack of examples. + If you have ARR without anti-knockback used, please send it to me. + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.90 最终幻想末世终迹 [the Dead Ends]"; + private const string Version = "0.0.0.3"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static readonly Vector3 Center = new Vector3(100, 0, 100); + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + [UserSetting("是否自动使用防击退(Auto anti-knockback)")] + public bool useaction { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + private Dictionary _tethersDict = new(); + private static bool _initHint = false; + + private readonly object CountLock = new object(); + + // Boss1 相关变量 + public bool isinRing = false; + public bool isNorthWind = true; + List vector3s = new(); + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + isinRing = false; + isNorthWind = true; + vector3s.Clear(); + + } + + // private static IGameObject? GetBossObject(ScriptAccessory sa) + // { + // return sa.GetByDataId(BossDataId).FirstOrDefault(); + // } + + #region 小怪 + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "末日绽放-Terminal Bloom", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26223"])] + public void TerminalBloom(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"TerminalBloom-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "TerminalBloom Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:26223"], userControl: false)] + public void TerminalBloomClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"TerminalBloom-{ev.SourceId}"); + } + + [ScriptMethod(name: "毒牙-Plague Fang", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28306"])] + public void PlagueFang(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(6f), 90, 2700, $"PlagueFang-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Plague Fang Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:28306"], userControl: false)] + public void PlagueFangClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"PlagueFang-{ev.SourceId}"); + } + + [ScriptMethod(name: "光子爆发-Photon Burst", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28273"])] + public void PhotonBurst(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(5f), 2700, $"PhotonBurst-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Photon Burst Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:28273"], userControl: false)] + public void PhotonBurstClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"PhotonBurst-{ev.SourceId}"); + } + #endregion + + + #region Boss1 + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + [ScriptMethod(name: "广域撒药-Medicine Field", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43798"])] + public void MedicineField(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "大AOE" : "Heavy AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "咳出-Cough Up", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25918"])] + public void CoughUp(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 3700, $"CoughUp-{ev.SourceId}", sa.Data.DefaultDangerColor); + + } + + [ScriptMethod(name: "孤独的绝望-Certain Solitude", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:28349"])] + public void CertainSolitude(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "至少两人站在一起" : "At least 2 players Stack"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "疾病激流-Wave of Nausea", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28347"])] + public async void WaveofNausea(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "月环靠近" : "Dynamo (In)"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + isinRing = true; + DebugMsg($"isinRing {isinRing}", sa); + await Task.Delay(10000); + isinRing = false; + DebugMsg($"isinRing {isinRing}", sa); + } + + + // S safepos {266.28, 501.01, -172.81} + // N Safepos {266.42, 501.01, -183.02} + // 等待月环后躲避aoe + // 00020080:N + // 01000010:S + [ScriptMethod(name: "WindCheck", eventType: EventTypeEnum.EnvControl, eventCondition: ["Id:regex:^(00020080|01000010)$", "Index:regex:^(0000002B)$"], userControl: false)] + public void WindCheck(Event ev, ScriptAccessory sa) + { + DebugMsg($" WindCheck {ev.Id} {ev.Index().ToString("X")}", sa); + if (ev.Id == 00020080) + { + isNorthWind = true; + } else if (ev.Id == 01000010) + { + isNorthWind = false; + } + DebugMsg($" isNorthWind: {isNorthWind}", sa); + } + [ScriptMethod(name: "test", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(28348)$"], userControl: false)] + public void test(Event ev, ScriptAccessory sa) + { + DebugMsg($" test efpos{ev.EffectPosition}", sa); + vector3s.Add(ev.EffectPosition); + if (vector3s.Count > 10) + { + for (int i = 0; i < vector3s.Count; i++) + { + for (int j = i + 1; j < vector3s.Count; j++) + { + if (vector3s[i].X == vector3s[j].X && vector3s[i].Y == vector3s[j].Y) + { + if (isDebug) sa.Log.Debug($"位置 {i} 和 {j} 的X, Y坐标相同: Vector3s[i]: {vector3s[i]} Vector3s[j]: {vector3s[j]}"); + } + } + } + } + } + [ScriptMethod(name: "致死腐烂毒素飞散-Necrotic Fluid", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25919)$"])] + public async void NecroticFluid(Event ev, ScriptAccessory sa) + { + // 1 + 6 + + // North Wind: Z + + // South Wind: Z - + + // Difference 6 + DebugMsg($"isNorthWind {isNorthWind}", sa); + Vector3 pos = ev.EffectPosition; + DrawHelper.DrawCircle(sa, pos, new Vector2(6f), 6200, $"NecroticFluid-{ev.SourceId}", sa.Data.DefaultDangerColor); + var delayTime = 6200; + for (int i = 0; i < 6; i++) + { + DebugMsg($"pos {pos}", sa); + if (pos.Z >= -157.5f || pos.Z <= -198.47f) break; + if (isNorthWind) + { + pos.Z += 6; + } + else + { + pos.Z -= 6; + } + DrawHelper.DrawCircle(sa, pos, new Vector2(6f), 2000, $"NecroticFluid-{ev.SourceId}", sa.Data.DefaultDangerColor, delay: delayTime, scaleByTime: false); + delayTime += 1400; + } + + + // 左上球efpos {"X":256.98,"Y":501.00,"Z":-194.51} + // 右上球efpos + // 左下球efpos + // 右下球efpos {"X":275.99,"Y":501.00,"Z":-161.52} + List ballList = new List + { + new Vector3(256.98f, 501.00f, -194.51f), + + new Vector3(275.99f, 501.00f, -161.52f), + }; + + + // 左上安全点 + // 右上安全点 {272.42, 501.01, -185.33} + // 左下安全点 {260.78, 501.01, -170.60} + // 右下安全点 + + List safePosList = new List + { + new Vector3(272.42f, 501.01f, -185.33f), + + new Vector3(260.78f, 501.01f, -170.60f), + }; + + // 上月环安全点 + // 下月环安全点 + + + List safeRingPosList = new List + { + new Vector3(266.47f, 501.01f, -183.05f), // N + new Vector3(266.47f, 501.01f, -172.84f) // S + }; + + await Task.Delay(2000); + if (isinRing) + { + DebugMsg("In ring", sa); + if (isNorthWind) + { + DebugMsg("north wind safe ring draw", sa); + if (isLead) DrawHelper.DrawDisplacement(sa, safeRingPosList[0], new Vector2(1f), 4700, "RingNorthWind", color: sa.Data.DefaultSafeColor); + if (isLead) DrawHelper.DrawDisplacement(sa, safePosList[0], new Vector2(1f), 4700, "NorthWindSafePos", color: sa.Data.DefaultSafeColor, delay: 4300); + } else + { + DebugMsg("South wind safe ring draw", sa); + if (isLead) DrawHelper.DrawDisplacement(sa, safeRingPosList[1], new Vector2(1f), 4700, "RingSouthWind", color: sa.Data.DefaultSafeColor); + if (isLead) DrawHelper.DrawDisplacement(sa, safePosList[1], new Vector2(1f), 4700, "SouthWindSafePos", color: sa.Data.DefaultSafeColor, delay: 4300); + } + } + else + { + DebugMsg("not in ring", sa); + if (isNorthWind) + { + DebugMsg("north wind safe draw", sa); + if (isLead) DrawHelper.DrawDisplacement(sa, safePosList[0], new Vector2(1f), 4700, "NorthWindSafePos", color: sa.Data.DefaultSafeColor); + + } else + { + DebugMsg("South wind safe draw", sa); + if (isLead) DrawHelper.DrawDisplacement(sa, safePosList[1], new Vector2(1f), 4700, "SouthWindSafePos", color: sa.Data.DefaultSafeColor); + } + } + + } + + [ScriptMethod(name: "痘疮之拳-Pox Flail", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25920"])] + public void PoxFlail(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "死刑点名" : "Tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + + [ScriptMethod(name: "腐水块-Blighted Water", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25922)$"])] + public void BlightedWater(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分摊点名" : "Stack"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6), 4900, "BlightedWater", color: sa.Data.DefaultSafeColor, scaleByTime: false); + + } + + #endregion + + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25936"])] + public void boss2AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "小口径射线 - Small-bore Laser", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28352"])] + public void SmallboreLaser(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(4, 20), 4700, $"SmallboreLaser-{ev.SourceId}"); + } + + [ScriptMethod(name: "分散提示 - Spread Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(28359|25928)$"])] + public void SpreadNotify(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分散不要重叠" : "Spread"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6), 4700, "Spread Notify", color: new Vector4(1,0,1,ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "和平炮 - Peacefire", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25934)$"])] + public void Peacefire(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10), 7000, $"Peacefire-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Peacefire cancel", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:regex:^(25934)$"])] + public void PeacefireCancel(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Peacefire-{ev.SourceId}"); + } + + [ScriptMethod(name: "大喷射 - Eclipsing Exhaust", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25931)$"])] + public void EclipsingExhaust(Event ev, ScriptAccessory sa) + { + //25931 + // 击退距离need + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "EclipsingExhaust-knock"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetPosition = ev.EffectPosition; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, 11f); + dp.DestoryAt = 4700; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + if (useaction) sa.Method.UseAction(sa.Data.Me, 7559); + if (useaction) sa.Method.UseAction(sa.Data.Me, 7548); + } + + + + [ScriptMethod(name: "坦克死刑 - Tank Buster", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25935)$"])] + public void Boss2TankBuster(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑激光点名" : "Laser buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? $"远离死刑激光" : $"Avoid buster Laser"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawRectObjectTarget(sa, ev.SourceId, ev.TargetId, new Vector2(10, 46), 5000, "Boss2TankBuster", color: sa.Data.DefaultDangerColor); + + } + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25950"])] + public void boss3AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "慈悲-Pity", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25949"])] + public void Pity(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "死刑点名" : "Tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "燐光 - Lamellar Light", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25939)$"])] + public void LamellarLight(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(15), 6000, $"LamellarLight-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "小蝴蝶燐光 - Butterfly Lamellar Light", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25942)$"])] + public void ButterflyLamellarLight(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(2, 30), 3000, $"ButterflyLamellarLight-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha)); + } + + [ScriptMethod(name: "博爱-Benevolence", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25946)$"])] + public void Benevolence(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分摊点名" : "Stack"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5100, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5100, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6), 5100, "Benevolence", color: sa.Data.DefaultSafeColor, scaleByTime: false); + + } + + [ScriptMethod(name: "慈爱之翼-Loving Embrace", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25943|25944)$"])] + public void LovingEmbrace(Event ev, ScriptAccessory sa) + { + // 25944 left + // 25943 right + var rot = float.Pi / 2; + if (ev.ActionId == 25943) rot = -float.Pi / 2; + DrawHelper.DrawFanObject(sa, ev.SourceId, rot, new Vector2(45f), 180, 6700, $"LovingEmbrace-{ev.SourceId}", scaleByTime: false, color: new Vector4(0, 1, 1, 1)); + + } + + [ScriptMethod(name: "安宁之翼 - Still Embrace", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25948)$"])] + public void StillEmbrace(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分散不要重叠" : "Spread"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5100, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6), 5100, "StillEmbrace Notify", color: new Vector4(1, 0, 1, 1), scaleByTime: true); + } + + #endregion + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } +} + +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); + } + } +} +#region 计算函数 + +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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, MarkType.Attack1, true); + sa.Method.Mark(0xE000000, MarkType.Attack2, true); + sa.Method.Mark(0xE000000, MarkType.Attack3, true); + sa.Method.Mark(0xE000000, MarkType.Attack4, true); + sa.Method.Mark(0xE000000, MarkType.Attack5, true); + sa.Method.Mark(0xE000000, MarkType.Attack6, true); + sa.Method.Mark(0xE000000, MarkType.Attack7, true); + sa.Method.Mark(0xE000000, MarkType.Attack8, true); + sa.Method.Mark(0xE000000, MarkType.Bind1, true); + sa.Method.Mark(0xE000000, MarkType.Bind2, true); + sa.Method.Mark(0xE000000, MarkType.Bind3, true); + sa.Method.Mark(0xE000000, MarkType.Stop1, true); + sa.Method.Mark(0xE000000, MarkType.Stop2, true); + sa.Method.Mark(0xE000000, MarkType.Square, true); + sa.Method.Mark(0xE000000, MarkType.Circle, true); + sa.Method.Mark(0xE000000, MarkType.Cross, true); + sa.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } +} + +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 + diff --git a/VeeverSW/06-EndWalker/the-Final-Day.cs b/VeeverSW/06-EndWalker/the-Final-Day.cs new file mode 100644 index 0000000..e26c128 --- /dev/null +++ b/VeeverSW/06-EndWalker/the-Final-Day.cs @@ -0,0 +1,1601 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; + + +namespace Veever.EndWalker.the_Final_Day; + +[ScriptType(name: Name, territorys: [997], guid: "eee45586-c0cc-4f43-9af1-6f0f4427cbf7", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + + +public class the_Final_Day +{ + const string NoteStr = + """ + v0.0.0.2 + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.90 终结之战 [the Final Day]"; + private const string Version = "0.0.0.2"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static readonly Vector3 Center = new Vector3(100, 0, 100); + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useAntiKnockback { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + public int HubrisTTSCount = 0; + private readonly object HubrisLock = new object(); + + private List tetherRecordList = new List(); + + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + //accessory.Method.SendChat($"/e [DEBUG] {str}"); + accessory.Log.Debug($"[DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + HubrisTTSCount = 0; + tetherRecordList.Clear(); + } + + private static IGameObject? GetBossObject(ScriptAccessory sa, uint BossDataId) + { + return sa.GetByDataId(BossDataId).FirstOrDefault(); + } + private uint westHandDataId = 18753; + private uint eastHandDataId = 18754; + + [ScriptMethod(name: "哀歌 - Elegeia", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26156|26242|26206)$"])] + public void Elegeia(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "终末狂热 - Telomania", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26207)$"])] + public void Telomania(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "连续多段AOE" : "Multi AOEs"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "天体撞击 - Stellar Collision", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26158)$"])] + public void StellarCollision(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(30), 6700, $"StellarCollision-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "银河 - Galaxie", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(27754)$"])] + public void Galaxie(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "靠近中心击退" : "Knockback from Center"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 6700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "Galaxie"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetPosition = ev.SourcePosition; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, 13); + dp.DestoryAt = 4700; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "反诘内侧 - Elenchos Inside", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26180)$"])] + public void ElenchosInside(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(14, 40), 5700, $"Elenchos Inside-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "反诘外侧 - Elenchos Outside", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26179)$"])] + public void ElenchosOutside(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(13, 40), 5700, $"Elenchos Outside-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 20)); + } + + [ScriptMethod(name: "反诘小怪 - Elenchos Mobs", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26174)$"])] + public void ElenchosMobs(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(5, 40), 4700, $"Elenchos Mobs-{ev.SourceId}", + sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "药毒 - Pharmakon", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26188)$"])] + public void Pharmakon(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(6), 1700, $"Pharmakon-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "坍缩星 - Dead Star", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(24142)$"])] + public void DeadStar(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(6), 2700, $"Dead Star-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + + [ScriptMethod(name: "傲慢 - Hubris", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26195)$"])] + public async void Hubris(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6), 4700, $"Pharmakon-{ev.SourceId}", + new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + + lock (HubrisLock) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "范围死刑,远离人群" : "AOE tankbuster — Stay away from party"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else if (HubrisTTSCount == 0) + { + string msg = language == Language.Chinese ? "远离范围死刑" : "Avoid AOE tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + HubrisTTSCount++; + } + + await Task.Delay(4700); + HubrisTTSCount = 0; + } + + [ScriptMethod(name: "Tether record", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(00A6)$"], userControl: false)] + public void TetherRecord(Event ev, ScriptAccessory sa) + { + if (tetherRecordList.Contains(ev.TargetId)) return; + tetherRecordList.Add(ev.TargetId); + if (isDebug) sa.Log.Debug($"Add {ev.TargetId} to List"); + } + + [ScriptMethod(name: "宿命 - Fatalism", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26162)$"])] + public void Fatalism(Event ev, ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug($"List: {tetherRecordList[0]}, List1: {tetherRecordList}"); + if (tetherRecordList.Count == 1) + { + DrawHelper.DrawCircleObject(sa, tetherRecordList[0], new Vector2(30), 8700, + $"StellarCollision-{ev.SourceId}", sa.Data.DefaultDangerColor, delay: 11000); + } + + if (tetherRecordList.Count == 2) + { + DrawHelper.DrawCircleObject(sa, tetherRecordList[1], new Vector2(30), 10500, + $"StellarCollision-{ev.SourceId}", sa.Data.DefaultDangerColor, delay: 9000); + } + } + + private List EpigonoiVectors = new List + { + new Vector3(86.64f, 0.00f, 86.55f), + new Vector3(113.76f, -0.00f, 86.33f), + new Vector3(113.41f, 0.00f, 113.47f), + new Vector3(86.73f, 0.00f, 113.21f) + }; + + [ScriptMethod(name: "后裔 - Epigonoi", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26182)$"], suppress: 3000)] + public void Epigonoi(Event ev, ScriptAccessory sa) + { + for (int i = 0; i < EpigonoiVectors.Count; i++) + { + DrawHelper.DrawCircle(sa, EpigonoiVectors[i], new Vector2(1f), 20000, $"Epigonoi: {EpigonoiVectors[i]}", color: sa.Data.DefaultSafeColor, + scaleByTime: false); + } + } + + + [ScriptMethod(name: "冲撞 - Crash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26199)$"])] + public void Crash(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(15f), 8700, $"Crash: {ev.EffectPosition}", color: sa.Data.DefaultDangerColor, + scaleByTime: true); + } + + + [ScriptMethod(name: "终极命运 - Ultimate Fate", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(27481)$"])] + public void UltimateFate(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "坦克LB" : "Tank LB"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } +} + +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; + } + } +} +#region 计算函数 + +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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, MarkType.Attack1, true); + sa.Method.Mark(0xE000000, MarkType.Attack2, true); + sa.Method.Mark(0xE000000, MarkType.Attack3, true); + sa.Method.Mark(0xE000000, MarkType.Attack4, true); + sa.Method.Mark(0xE000000, MarkType.Attack5, true); + sa.Method.Mark(0xE000000, MarkType.Attack6, true); + sa.Method.Mark(0xE000000, MarkType.Attack7, true); + sa.Method.Mark(0xE000000, MarkType.Attack8, true); + sa.Method.Mark(0xE000000, MarkType.Bind1, true); + sa.Method.Mark(0xE000000, MarkType.Bind2, true); + sa.Method.Mark(0xE000000, MarkType.Bind3, true); + sa.Method.Mark(0xE000000, MarkType.Stop1, true); + sa.Method.Mark(0xE000000, MarkType.Stop2, true); + sa.Method.Mark(0xE000000, MarkType.Square, true); + sa.Method.Mark(0xE000000, MarkType.Circle, true); + sa.Method.Mark(0xE000000, MarkType.Cross, true); + sa.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 diff --git a/VeeverSW/06-EndWalker/the-Minstrels-Ballad-Endsingers-Aria.cs b/VeeverSW/06-EndWalker/the-Minstrels-Ballad-Endsingers-Aria.cs new file mode 100644 index 0000000..10b4323 --- /dev/null +++ b/VeeverSW/06-EndWalker/the-Minstrels-Ballad-Endsingers-Aria.cs @@ -0,0 +1,291 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; + +namespace Veever.EndWalker.theMinstrelsBalladEndsingersAria; + +[ScriptType(name: "LV.90 终极之战(解限版)", territorys: [998], guid: "100df6f8-d8ce-44f7-9fb0-431eca0f2825", + version: "0.0.0.7", author: "Veever", note: noteStr)] + +public class the_Minstrels_Ballad_Endsingers_Aria +{ + const string noteStr = + """ + v0.0.0.7: + 1. 现在支持文字横幅/TTS开关 + 2. 以前的这几个脚本的底层扩展目前懒得重构(就能加啥随便加了) + 鸭门。 + """; + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + public int connectNotify = 0; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "绝望的锁链", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28701"])] + public void GripofDespair(Event @event, ScriptAccessory accessory) + { + List vectorList = new List + { + new Vector3(92.89f, 0.00f, 87.07f), + new Vector3(92.96f, 0.00f, 95.95f), + new Vector3(92.97f, 0.00f, 104.98f), + new Vector3(93.06f, 0.00f, 113.93f), + new Vector3(107.00f, 0.00f, 86.94f), + new Vector3(107.13f, 0.00f, 96.03f), + new Vector3(107.28f, 0.00f, 105.11f), + new Vector3(107.03f, 0.00f, 114.14f), + }; + + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + + if (index == -1) + { + return; + } + + bool isLeftSide = index >= 0 && index <= 3; + bool isRightSide = index >= 4 && index <= 7; + + if (isLeftSide || isRightSide) + { + string sideText = isLeftSide ? "左侧" : "右侧"; + if (isText) accessory.Method.TextInfo($"场中集合准备向{sideText}拉线", duration: 4700, true); + if (isTTS) + { + accessory.Method.TTS($"场中集合准备向{sideText}拉线"); + } + + Vector3 pos = isLeftSide + ? new Vector3(81.38f, 0.00f, 102.54f) + : new Vector3(118.63f, 0.00f, 99.46f); + + Vector3 pos1 = vectorList[index]; + + DrawDisplacement(accessory, "连线踩塔指路1", pos, 4700, 5000); + DrawDisplacement(accessory, "连线踩塔指路2", pos1, 9700, 5000); + } + } + + private void DrawDisplacement(ScriptAccessory accessory, string name, Vector3 targetPosition, int delay, int destroyAt) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(2); + dp.Delay = delay; + dp.DestoryAt = destroyAt; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "反诘内侧危险", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28706"])] + public void ElenchosInside(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Elenchos"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Position = new Vector3(100f, 0f, 80f); + dp.Owner = @event.SourceId(); + dp.TargetPosition = new Vector3(100f, 0f, 100f); + //dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(14, 40); + dp.DestoryAt = 7700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "反诘外侧危险", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28705"])] + public void ElenchosOutside(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Elenchos"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Position = new Vector3(100f, 0f, 80f); + dp.TargetPosition = new Vector3(100f, 0f, 100f); + dp.Scale = new Vector2(14, 40); + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "傲慢", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28717"])] + public void Hubris(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("双T死刑", duration: 4700, true); + if (isTTS) accessory.Method.TTS("双T死刑"); + } + + [ScriptMethod(name: "AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(28718|28662)$"])] + public void AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + if (isTTS) accessory.Method.TTS("AOE"); + } + + [ScriptMethod(name: "反讽", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28720"])] + public void Eironeia(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo($"分组分摊", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"分组分摊"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "反讽(分摊)"; + dp.Color = new Vector4(0 / 255.0f, 255 / 255.0f, 255 / 255.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "分离", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28668"])] + public void Diairesis(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Sweeping Gouge"; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(20); + dp.Radian = float.Pi / 180 * 180; + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "蓝色天体撞击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(286[67]7)$"])] + public void BlueStar(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("击退到安全位置", duration: 4700, true); + if (isTTS) accessory.Method.TTS("击退到安全位置"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "蓝色天体撞击"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 6700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"蓝色天体击退"; + dp1.Scale = new(1.5f, 23); + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.Owner = accessory.Data.Me; + dp1.TargetPosition = @event.TargetPosition(); + dp1.Rotation = float.Pi; + dp1.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + + [ScriptMethod(name: "红色天体撞击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28666"])] + public void RedStar(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "红色天体撞击"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(30); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } +} + + + + +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 string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + public static uint SourceRotation(this Event @event) + { + return ParseHexId(@event["SourceRotation"], out var sourceRotation) ? sourceRotation : 0; + } + + 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 JsonConvert.DeserializeObject(@event["SourceName"]) ?? string.Empty; + } + + 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"]); + } +} + diff --git a/VeeverSW/06-EndWalker/the-Tower-of-Zot.cs b/VeeverSW/06-EndWalker/the-Tower-of-Zot.cs new file mode 100644 index 0000000..479b535 --- /dev/null +++ b/VeeverSW/06-EndWalker/the-Tower-of-Zot.cs @@ -0,0 +1,600 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; + +namespace Veever.EndWalker.theTowerofZot; + +[ScriptType(name: "LV.81 异形楼阁佐特塔", territorys: [952], guid: "98a97134-f87b-4386-aad9-2a99e81794ab", + version: "0.0.0.5", author: "Veever", note: noteStr)] + +public class the_Tower_of_Zot +{ + const string noteStr = + """ + v0.0.0.5: + 1. 现在支持文字横幅/TTS开关 + 2. 标点开关以及本地开关都在用户设置里面,可自行选择关闭或者开启(默认本地开启) + 3. Boss1击杀后会生成门指路,绿圈显示为击杀小怪后会打开的门(防止晕头转向) + 4. Boss2标记目前有bug删除不掉,但是正常dps的话到那里boss也快死了(不正常dps一个攻击1的标点也不影响),所以有缘会修 + 5. 强烈建议默认本地标点,因为是秒标记(不拟人) + 6. 建议关闭cactbot播报功能以防止重复播报 + 鸭门。 + """; + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + [UserSetting("标点开关")] + public bool isMark { get; set; } = true; + + [UserSetting("本地标点开关(打开则为本地开关,关闭则为小队)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + public int berserkerSpheresCount; + public int PrakamyaSiddhiCount; + public int DeltaFireIIICount; + + private readonly object berserkerSpheresLock = new object(); + private readonly object DeltaFireIIILock = new object(); + private bool isDRTTS = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + accessory.Method.MarkClear(); + berserkerSpheresCount = 0; + PrakamyaSiddhiCount = 0; + DeltaFireIIICount = 0; + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + #region 小怪 + [ScriptMethod(name: "安神毒气", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27874"])] + public async void SoporificGas(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"安神毒气"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(9f); + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"安神毒气描边"; + dp1.Scale = new(9f); + dp1.InnerScale = new(8.98f); + dp1.Radian = float.Pi * 2; + dp1.Color = new Vector4(178 / 255.0f, 34 / 255.0f, 34 / 255.0f, 10.0f); + dp1.Owner = @event.SourceId(); + dp1.DestoryAt = 4000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + + if (isText) accessory.Method.TextInfo("钢铁, 可打断", duration: 4000, true); + accessory.TTS("钢铁, 可打断", isTTS, isDRTTS); + } + + + + [ScriptMethod(name: "魔导汽油弹", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:24138"])] + public void GarleanFire(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "魔导汽油弹"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(8f); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "左臂斩击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:24140"])] + public void LeftArmSlash(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "左臂斩击"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10f); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Boss1 - Boss2 指路", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:13294"])] + public async void Navi(Event @event, ScriptAccessory accessory) + { + await Task.Delay(50); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Navi1"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Position = new Vector3(-300.14f, -185.02f, 159.79f); + dp.Scale = new Vector2(1.55f); + dp.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "Navi2"; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.Position = new Vector3(-330.10f, -181.00f, 82.42f); + dp1.Scale = new Vector2(1.55f); + dp1.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = "Navi3"; + dp2.Color = accessory.Data.DefaultSafeColor; + dp2.Position = new Vector3(-238.35f, -172.03f, 60.46f); + dp2.Scale = new Vector2(1.55f); + dp2.DestoryAt = long.MaxValue; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + } + + [ScriptMethod(name: "删除 Boss1 - Boss2 指路", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:13295"])] + public void DelNavi(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "扩散射线", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:24147"])] + public void DiffusionRay(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扩散射线"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(5); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + + #endregion + + #region Boss1 + [ScriptMethod(name: "Boss1死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25248"])] + public void Boss1Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("带毒死刑准备", duration: 4000, true); + accessory.TTS("带毒死刑准备", isTTS, isDRTTS); + } + + [ScriptMethod(name: "人趣冰封", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25238"])] + public void ManusyaBlizzardIII(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "人趣冰封"; + //dp.Color = accessory.Data.DefaultDangerColor; + dp.Color = new Vector4(0 / 255.0f, 255 / 255.0f, 255 / 255.0f, 1f); + //dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(40f); + dp.Radian = float.Pi / 180 * 20; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "人趣爆炎", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25237"])] + public void ManusyaFireIII(Event @event, ScriptAccessory accessory) + { + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = $"人趣爆炎(单月环)"; + dp2.Color = accessory.Data.DefaultSafeColor; + dp2.Owner = @event.SourceId(); + dp2.Scale = new Vector2(5); + dp2.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + + var dp3 = accessory.Data.GetDefaultDrawProperties(); + dp3.Name = $"人趣爆炎指路"; + dp3.Owner = accessory.Data.Me; + dp3.Color = accessory.Data.DefaultSafeColor; + dp3.ScaleMode |= ScaleMode.YByDistance; + dp3.TargetPosition = @event.SourcePosition(); + dp3.Scale = new(2); + dp3.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + } + + [ScriptMethod(name: "人趣暴雷", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25239"])] + public void ManusyaThunderIII(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"人趣暴雷"; + dp.Color = new Vector4(123 / 255.0f, 104 / 255.0f, 238 / 255.0f, 2f); + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(3); + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "人趣剧毒菌", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25240"])] + public void ManusyaBioIII(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"人趣剧毒菌"; + dp.Color = new Vector4(50 / 255.0f, 205 / 255.0f, 50 / 255.0f, 1.5f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(40); + dp.Radian = float.Pi / 180 * 180; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + if (isText) accessory.Method.TextInfo("去Boss身后", duration: 4000, true); + accessory.TTS("去Boss身后", isTTS, isDRTTS); + } + + [ScriptMethod(name: "Boss1死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25242"])] + public void TransmuteFireIII(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("准备目标点月环, 先靠近Boss注意下一个机制", duration: 3000, true); + accessory.TTS("准备目标点月环, 先靠近Boss注意下一个机制", isTTS, isDRTTS); + } + + [ScriptMethod(name: "德鲁帕德", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25244"])] + public void Dhrupad(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("类AOE, 奶妈注意团血", duration: 4000, true); + accessory.TTS("类AOE, 奶妈注意团血", isTTS, isDRTTS); + } + + #endregion + + #region Boss2 + [ScriptMethod(name: "Boss2死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25257"])] + public void Boss2Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 3700, true); + accessory.TTS("死刑准备", isTTS, isDRTTS); + } + + [ScriptMethod(name: "身所达", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25256"])] + public void PraptiSiddhi(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "身所达"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(4, 40); + dp.ScaleMode = ScaleMode.ByTime; + dp.Delay = 400; + dp.DestoryAt = 1600; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "狂暴晶球钢铁", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:13296"])] + public void berserkerSpheres(Event @event, ScriptAccessory accessory) + { + lock (berserkerSpheresLock) + { + if (berserkerSpheresCount == 0) + { + if (isText) accessory.Method.TextInfo("去安全区", duration: 6000, true); + accessory.TTS("去安全区", isTTS, isDRTTS); + } + if (berserkerSpheresCount == 5) + { + if (isText) accessory.Method.TextInfo("去安全区, 准备攻击Boss", duration: 6000, true); + accessory.TTS("去安全区, 准备攻击Boss", isTTS, isDRTTS); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "狂暴晶球钢铁"; + //dp.Color = accessory.Data.DefaultDangerColor; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = @event.SourcePosition(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(15f); + dp.DestoryAt = (berserkerSpheresCount <= 4) ? 11501 : 20300; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + DebugMsg($"berserkerSpheresCount: {berserkerSpheresCount}", accessory); + berserkerSpheresCount++; + } + } + + [ScriptMethod(name: "标记Boss", eventType: EventTypeEnum.MorelogCompat, eventCondition: ["Id:0197", "SourceDataId:13295"])] + public async void markBoss(Event @event, ScriptAccessory accessory) + { + DebugMsg("markBoss", accessory); + if (isMark) accessory.Method.Mark(@event.SourceId(), KodakkuAssist.Module.GameOperate.MarkType.Attack1, LocalMark); + } + + [ScriptMethod(name: "大愿成", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25251"])] + public void PrakamyaSiddhi(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("钢铁, 远离Boss", duration: 3700, true); + accessory.TTS("钢铁, 远离Boss", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "狂暴晶球钢铁"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(5f); + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (PrakamyaSiddhiCount > 0) + { + DebugMsg("clear markBoss", accessory); + accessory.Method.MarkClear(); + DebugMsg("Finish clear markBoss", accessory); + } + PrakamyaSiddhiCount++; + } + + [ScriptMethod(name: "人趣停止", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25255"])] + public async void ManusyaStop(Event @event, ScriptAccessory accessory) + { + await Task.Delay(3500); + if (isText) accessory.Method.TextInfo("分散,不要站在同一条直线上", duration: 3700, true); + accessory.TTS("分散,不要站在同一条直线上", isTTS, isDRTTS); + } + #endregion + + #region Boss3 + [ScriptMethod(name: "三角爆炎分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25265"])] + public void DeltaFireIII(Event @event, ScriptAccessory accessory) + { + lock (DeltaFireIIILock) + { + if (DeltaFireIIICount == 0) + { + if (isText) accessory.Method.TextInfo("先月环, 再分散", duration: 4000, true); + accessory.TTS("先月环, 再分散", isTTS, isDRTTS); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"三角爆炎分散"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(6); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "Boss3身所达", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25275"])] + public void Boss3PraptiSiddhi(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Boss3身所达"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(4, 40); + dp.ScaleMode = ScaleMode.ByTime; + dp.Delay = 400; + dp.DestoryAt = 1600; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "Boss3死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25274|25280)$"])] + public void Boss3Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑 + 类AOE, 奶妈注意团血", duration: 4000, true); + accessory.TTS("死刑加类AOE, 奶妈注意团血", isTTS, isDRTTS); + } + + // 瞎眼睛, 删掉 + //[ScriptMethod(name: "三角暴雷", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25272|25269|25270)$"])] + //public void DeltaThunderIII(Event @event, ScriptAccessory accessory) + //{ + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"三角暴雷"; + // dp.Color = new Vector4(123 / 255.0f, 104 / 255.0f, 238 / 255.0f, 2f); + // dp.Position = @event.EffectPosition(); + // dp.Scale = new Vector2(3); + // dp.DestoryAt = 4000; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + //} + + // 每次aoe和死刑都在一起,so合并 + //[ScriptMethod(name: "Boss3德鲁帕德", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25281"])] + //public void Boss3Dhrupad(Event @event, ScriptAccessory accessory) + //{ + // if (isText) accessory.Method.TextInfo("类AOE, 奶妈注意团血", duration: 4000, true); + // accessory.TTS("类AOE, 奶妈注意团血", isTTS, isDRTTS); + //} + + [ScriptMethod(name: "三角爆炎月环", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25263"])] + public void DeltaFire1III(Event @event, ScriptAccessory accessory) + { + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = $"三角爆炎(单月环)"; + dp2.Color = accessory.Data.DefaultSafeColor; + dp2.Position = @event.EffectPosition(); + dp2.Scale = new Vector2(5); + dp2.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + + var dp3 = accessory.Data.GetDefaultDrawProperties(); + dp3.Name = $"三角爆炎指路"; + dp3.Owner = accessory.Data.Me; + dp3.Color = accessory.Data.DefaultSafeColor; + dp3.ScaleMode |= ScaleMode.YByDistance; + dp3.TargetPosition = @event.EffectPosition(); + dp3.Scale = new(2); + dp3.DestoryAt = 2500; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + } + + [ScriptMethod(name: "Boss3分摊", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25272"])] + public void Boss3Stack(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Boss3分摊"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 5000; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 5000, true); + accessory.TTS($"与{tname}分摊", isTTS, isDRTTS); + } + #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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + accessory.Method.TTS(text); + } +} + diff --git a/VeeverSW/07-DawnTrail/Cenote-Ja-Ja-Gural.cs b/VeeverSW/07-DawnTrail/Cenote-Ja-Ja-Gural.cs new file mode 100644 index 0000000..306dfb4 --- /dev/null +++ b/VeeverSW/07-DawnTrail/Cenote-Ja-Ja-Gural.cs @@ -0,0 +1,313 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using FFXIVClientStructs; +using System; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +namespace Veever.DawnTrail.Cenote_Ja_Ja_Gural; + +[ScriptType(name: "加加财富天坑", territorys: [1209], guid: "64620549-e269-49c0-b0ae-76edec024864", + version: "0.0.0.4", author: "Veever", note: noteStr)] + +public class Cenote_Ja_Ja_Gural +{ + const string noteStr = + """ + v0.0.0.4: + 1. 加加财富天坑只根据自己下底的arr进行绘制,没有后面天箭绘画的情况 + 2. 如果你有此方面的素材可以dc私信提供给我,我会加入到绘制里 + 鸭门。 + """; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + public int AetherialLightCount; + + private readonly object AetherialLightLock = new object(); + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + AetherialLightCount = 0; + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "保护色", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3997"])] + public void Crypsis(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "保护色"; + dp.Color = new Vector4(0.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(5f); + dp.InnerScale = new Vector2(4.9f); + dp.DestoryAt = 10000; + dp.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp); + } + + [ScriptMethod(name: "黄金飞溅", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:38267"])] + public void GoldenGall(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 = float.Pi; + dp.DestoryAt = 6700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "以太光", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:38271"])] + public async void AetherialLight(Event @event, ScriptAccessory accessory) + { + lock (AetherialLightLock) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"以太光"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(40f); + dp.Radian = float.Pi / 180 * 60; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"以太光安全"; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.Owner = @event.SourceId(); + dp1.Scale = new Vector2(40f); + dp1.Radian = float.Pi / 180 * 60; + dp1.Delay = 5000; + dp1.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp1); + } + if (AetherialLightCount >= 5) + { + await Task.Delay(5000); + accessory.Method.RemoveDraw(".*"); + } + DebugMsg($"{AetherialLightCount}", accessory); + AetherialLightCount++; + } + + [ScriptMethod(name: "火焰剑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^3825[15]$"])] + public void FlameBlade(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Flame Blade"; + dp.Owner = @event.SourceId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new(5f, 40f); + dp.DestoryAt = 2200; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } +} + +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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + + +public static class Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + if (isDRTTS) + { + accessory.Method.SendChat($"/pdr tts {text}"); + } + else if (isTTS) + { + accessory.Method.TTS(text); + } + } +} + +public static class IbcHelper +{ + public static IBattleChara? GetById(ScriptAccessory accessory, uint id) + { + return (IBattleChara?)accessory.Data.Objects.SearchByEntityId(id); + } + + public static IBattleChara? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me) as IBattleChara; + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } +} + +public static unsafe class IBattleCharaExtensions +{ + public static bool HasStatus(this IBattleChara ibc, uint statusId, float remaining = -1) + { + return ibc.StatusList.Any(s => s.StatusId == statusId && s.RemainingTime > remaining); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } + +} + diff --git a/VeeverSW/07-DawnTrail/Everkeep.cs b/VeeverSW/07-DawnTrail/Everkeep.cs new file mode 100644 index 0000000..d8322e4 --- /dev/null +++ b/VeeverSW/07-DawnTrail/Everkeep.cs @@ -0,0 +1,711 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; +using System.Timers; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Veever.DawnTrail.Everkeep; + +[ScriptType(name: "LV.99 佐拉加歼灭战", territorys: [1200], guid: "7a6d317c-b176-4e94-9fbc-3bc833be1338", + version: "0.0.1.0", author: "Veever", note: noteStr)] + +public class Everkeep +{ + const string noteStr = + """ + v0.0.1.0: + 1. 现在支持文字横幅/TTS开关 + 鸭门。 + 2. 现已支持所有利刃冲情况,如果依然遇到画错/漏画的情况,请dc带回放私信我(十分感谢) + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + [UserSetting("Debug开关")] + public bool isDebug { get; set; } = false; + + public int DoubleEdgedSwordsCount; + public int BitterReapingCount; + public int Run0178TTS; + public int ForgedTrackCount; + + private readonly object BitterReapingLock = new object(); + private readonly object Run0178Lock = new object(); + private readonly object ForgedTrackLock = new object(); + + private bool isDRTTS = false; + + + public Vector3 midpoint = new Vector3(100.0f, 0.0f, 100.0f); + public int midpointSum = 100 + 0 + 100; + private static readonly float platformOffset = 30 / MathF.Sqrt(2); + + + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + DoubleEdgedSwordsCount = 0; + BitterReapingCount = 0; + Run0178TTS = 0; + ForgedTrackCount = 0; + } + + [ScriptMethod(name: "灵魂超载", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3770[78])$"])] + public void TerrorUnleashed(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + accessory.TTS("AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "双锋合刃", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37714"])] + public void DoubleEdgedSwords(Event @event, ScriptAccessory accessory) + { + if (DoubleEdgedSwordsCount == 0) + { + if (isDebug) accessory.Method.SendChat($"/e SourceRotation: {@event.SourceRotation()}"); + if (isText) accessory.Method.TextInfo($"去Boss{((@event.SourceRotation() == 3.14f) ? "后" : "前")}面,然后准备对穿", duration: 4500, true); + accessory.TTS($"去Boss{((@event.SourceRotation() == 3.14f) ? "后" : "前")}面,然后准备对穿", isTTS, isDRTTS); + } + + DoubleEdgedSwordsCount++; + if (DoubleEdgedSwordsCount == 2) + { + DoubleEdgedSwordsCount = 0; + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "DoubleEdged Swords"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 0.5f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(30); + dp.Radian = float.Pi / 180 * 180; + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "弑父之愤火", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37715"])] + public void TankBuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4700, true); + accessory.TTS("死刑准备", isTTS, isDRTTS); + } + + [ScriptMethod(name: "民众的幻影(爆炸)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37709"])] + public void shadowExplosion1(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "shadowExplosion1"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 0.5f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(8); + dp.DestoryAt = 7700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "shadowExplosion1描边"; + dp1.Scale = new(8f); + dp1.InnerScale = new(7.95f); + dp1.Radian = float.Pi * 2; + dp1.Color = new Vector4(178 / 255.0f, 34 / 255.0f, 34 / 255.0f, 9.0f); + dp1.Position = @event.EffectPosition(); + dp1.DestoryAt = 7700; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp1); + } + + [ScriptMethod(name: "利刃寻迹", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37712"])] + public void VorpalTrail(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "VorpalTrail"; + //dp.Color = accessory.Data.DefaultDangerColor; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Position = @event.SourcePosition(); + dp.TargetPosition = @event.EffectPosition(); + dp.Scale = new Vector2(4); + dp.DestoryAt = 2300; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "回旋惩击(钢铁)/(月环)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3773[45])$"])] + public void SmitingCircuit(Event @event, ScriptAccessory accessory) + { + if (@event.ActionId() == 37735) + { + if (isText) accessory.Method.TextInfo("钢铁, 远离Boss", duration: 4700, true); + accessory.TTS("钢铁, 远离Boss", isTTS, isDRTTS); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "SmitingCircuit(钢铁)"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } else if (@event.ActionId() == 37734) + { + if (isText) accessory.Method.TextInfo("月环, 去Boss脚下", duration: 4700, true); + accessory.TTS("月环, 去Boss脚下", isTTS, isDRTTS); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "SmitingCircuit(月环)"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "新曦世纪", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3771[68])$"])] + public void DawnofanAge(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("转场AOE", duration: 6700, true); + accessory.TTS("转场AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "无敌裂斩", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37720"])] + public void ChasmofVollok(Event @event, ScriptAccessory accessory) + { + bool inArena = true; + Vector3 spos = @event.SourcePosition(); + var posSum = spos.X + spos.Y + spos.Z; + + // SE + if (spos.X >= 107.05f && spos.Z >= 107.05f) + { + inArena = false; + } + + // NE + if (spos.X >= 107.05f && spos.X <= 135.3f && spos.Z <= 92.93f && spos.Z >= 64.77f) + { + inArena = false; + } + + // NW + if (spos.X >= 64.66f && spos.X <= 92.83 && spos.Z <= 92.8f && spos.Z >= 64.84f) + { + inArena = false; + } + + // WS + if (spos.X >= 64.66f && spos.X <= 92.89 && spos.Z <= 135.3f && spos.Z >= 107.1f) + { + inArena = false; + } + if (isDebug) accessory.Method.SendChat($"/e {inArena}"); + if (inArena) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "无敌裂斩"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 0.5f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(5, 5); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } else + { + if (isDebug) accessory.Method.SendChat($"/e In 偏移"); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "无敌裂斩偏移"; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 0.5f); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Scale = new Vector2(5, 5); + dp.DestoryAt = 7000; + var offset = new Vector3( + @event.SourcePosition().X > midpoint.X ? -platformOffset : +platformOffset, + 0, + @event.SourcePosition().Z > midpoint.Z ? -platformOffset : +platformOffset + ); + dp.Position = @event.SourcePosition() + offset; + dp.TargetPosition = @event.SourcePosition(); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + } + + [ScriptMethod(name: "愤恨收割", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37754"])] + public void BitterReaping(Event @event, ScriptAccessory accessory) + { + lock (BitterReapingLock) + { + if (isDebug) accessory.Method.SendChat($"/e BitterReapingCount: {BitterReapingCount}"); + if (BitterReapingCount == 0 || BitterReapingCount == 2 || BitterReapingCount == 4) + { + if (isText) accessory.Method.TextInfo("双T死刑", duration: 4700, true); + accessory.TTS("双T死刑", isTTS, isDRTTS); + } + BitterReapingCount++; + } + } + + [ScriptMethod(name: "利刃冲", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37729"])] + public void ForgedTrack(Event @event, ScriptAccessory accessory) + { + lock (ForgedTrackLock) + { + // Always XX + List NEList = new List + { + new Vector3(122.98f, 0.00f, 66.41f), + new Vector3(130.05f, 0.00f, 73.48f), + new Vector3(133.59f, 0.00f, 77.02f), + new Vector3(126.52f, 0.00f, 69.95f) + }; + + // Always Big XX + List NWList = new List + { + new Vector3(69.95f, 0.00f, 73.48f), + new Vector3(77.02f, 0.00f, 66.41f), + new Vector3(73.48f, 0.00f, 69.95f), + new Vector3(66.41f, 0.00f, 77.02f) + }; + + // Always XX + List SWList = new List + { + new Vector3(66.41f, 0.00f, 122.98f), + new Vector3(73.48f, 0.00f, 130.05f), + new Vector3(69.95f, 0.00f, 126.52f), + new Vector3(77.02f, 0.00f, 133.59f) + }; + + // Always Big XX + List SEList = new List + { + new Vector3(133.59f, 0.00f, 122.98f), + new Vector3(126.52f, 0.00f, 130.05f), + new Vector3(122.98f, 0.00f, 133.59f), + new Vector3(130.05f, 0.00f, 126.52f) + }; + + List vectorList = new List + { + //From NE (left to Right) X-- Z++ + new Vector3(101.55f, 0.00f, 87.62f), //[0] + new Vector3(105.16f, 0.00f, 91.15f), //[1] + new Vector3(108.68f, 0.00f, 94.74f), //[2] + new Vector3(112.29f, 0.00f, 98.33f), //[3] + + //From NW (left to Right) X-- Z++ + new Vector3(87.60f, 0.00f, 98.29f), //[4] + new Vector3(91.15f, 0.00f, 94.71f), //[5] + new Vector3(94.78f, 0.00f, 91.14f), //[6] + new Vector3(98.31f, 0.00f, 87.61f), //[7] + }; + + var spos = @event.SourcePosition(); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Duty's Edge"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = @event.SourcePosition(); + var offset = new Vector3( + @event.SourcePosition().X > midpoint.X ? -platformOffset : +platformOffset, + 0, + @event.SourcePosition().Z > midpoint.Z ? -platformOffset : +platformOffset + ); + dp.TargetPosition = @event.SourcePosition() + offset; + dp.Scale = new Vector2(5, 20); + dp.DestoryAt = 11599; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + // handle Method + // NEList + #region NEList + if (@event.SourcePosition() == NEList[0]) + { + if (isDebug) accessory.Method.SendChat($"/e NEList[0]"); + var dpPos = vectorList[1]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == NEList[1]) + { + if (isDebug) accessory.Method.SendChat($"/e NEList[1]"); + var dpPos = vectorList[3]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == NEList[2]) + { + if (isDebug) accessory.Method.SendChat($"/e NEList[2]"); + var dpPos = vectorList[0]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == NEList[3]) + { + if (isDebug) accessory.Method.SendChat($"/e NEList[3]"); + var dpPos = vectorList[2]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + #endregion + // NWList + #region NWList + if (@event.SourcePosition() == NWList[0]) + { + if (isDebug) accessory.Method.SendChat($"/e NWList[0]"); + var dpPos = vectorList[7]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == NWList[1]) + { + if (isDebug) accessory.Method.SendChat($"/e NWList[1]"); + var dpPos = vectorList[5]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == NWList[2]) + { + if (isDebug) accessory.Method.SendChat($"/e NWList[2]"); + var dpPos = vectorList[4]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == NWList[3]) + { + if (isDebug) accessory.Method.SendChat($"/e NWList[3]"); + var dpPos = vectorList[6]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + #endregion + // SWList + #region SWList + if (@event.SourcePosition() == SWList[0]) + { + if (isDebug) accessory.Method.SendChat($"/e SWList[0]"); + var dpPos = vectorList[1]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == SWList[1]) + { + if (isDebug) accessory.Method.SendChat($"/e SWList[1]"); + var dpPos = vectorList[3]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == SWList[2]) + { + if (isDebug) accessory.Method.SendChat($"/e SWList[2]"); + var dpPos = vectorList[0]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == SWList[3]) + { + if (isDebug) accessory.Method.SendChat($"/e SWList[3]"); + var dpPos = vectorList[2]; + var tPos = dpPos; + tPos.X = tPos.X - 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + #endregion + // SEList + #region SEList + if (@event.SourcePosition() == SEList[0]) + { + if (isDebug) accessory.Method.SendChat($"/e SEList[0]"); + var dpPos = vectorList[5]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == SEList[1]) + { + if (isDebug) accessory.Method.SendChat($"/e SEList[1]"); + var dpPos = vectorList[7]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == SEList[2]) + { + if (isDebug) accessory.Method.SendChat($"/e SEList[2]"); + var dpPos = vectorList[6]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + if (@event.SourcePosition() == SEList[3]) + { + if (isDebug) accessory.Method.SendChat($"/e SEList[2]"); + var dpPos = vectorList[4]; + var tPos = dpPos; + tPos.X = tPos.X + 4; + tPos.Z = tPos.Z + 4; + Draw(accessory, dpPos, tPos); + } + #endregion + ForgedTrackCount++; + } + } + + private void Draw(ScriptAccessory accessory, Vector3 dpPos, Vector3 dpTargetpos) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "ForgedTrack"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = dpPos; + dp.TargetPosition = dpTargetpos; + dp.Scale = new Vector2(5, 20); + dp.Delay = (ForgedTrackCount >= 8) ? 5500 : 0; + dp.DestoryAt = (ForgedTrackCount >= 8) ? 9099 : 13000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + if (isDebug) accessory.Method.SendChat($"/e ForgedTrackCount: {ForgedTrackCount}"); + } + + [ScriptMethod(name: "半身残", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:37738"])] + public void HalfFull(Event @event, ScriptAccessory accessory) + { + + if (isDebug) accessory.Method.SendChat($"/e SourceRotation: {@event.SourceRotation()}"); + if (isText) accessory.Method.TextInfo($"去Boss{((@event.SourceRotation() == 1.57f) ? "左" : "右")}侧", duration: 4500, true); + accessory.TTS($"去Boss{((@event.SourceRotation() == 1.57f) ? "左" : "右")}侧", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "HalfFull"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 0.5f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(30); + dp.Radian = float.Pi / 180 * 180; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "回旋半身残(钢铁/月环)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3774[123])$"])] + public void HalfCircuitFull(Event @event, ScriptAccessory accessory) + { + + if (isDebug) accessory.Method.SendChat($"/e SourceRotation: {@event.SourceRotation()}"); + //if (isText) accessory.Method.TextInfo($"去Boss{((@event.SourceRotation() == 1.57f) ? "左" : "右")}侧", duration: 4500, true); + //accessory.TTS($"去Boss{((@event.SourceRotation() == 1.57f) ? "左" : "右")}侧", isTTS, isDRTTS); + + if (@event.ActionId() == 37741) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "HalfFull with Circuit"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 0.5f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(30); + dp.Radian = float.Pi / 180 * 180; + dp.DestoryAt = 7300; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + if (@event.ActionId() == 37742) + { + if (isText) accessory.Method.TextInfo("月环, 注意躲避半场刀", duration: 4700, true); + accessory.TTS("月环, 注意躲避半场刀", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "HalfCircuit(月环)"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + if (@event.ActionId() == 37743) + { + if (isText) accessory.Method.TextInfo("钢铁, 远离Boss, 注意躲避半场刀", duration: 4700, true); + accessory.TTS("钢铁, 远离Boss, 注意躲避半场刀", isTTS, isDRTTS); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "HalfCircuit(钢铁)"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "分散", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0178"])] + public void Run0178(Event @event, ScriptAccessory accessory) + { + lock (Run0178Lock) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "0178分散"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 5500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (Run0178TTS == 0) + { + if (isText) accessory.Method.TextInfo("全体分散", duration: 4700, true); + accessory.TTS("全体分散", isTTS, isDRTTS); + } + Run0178TTS++; + } + } + + [ScriptMethod(name: "责任之刃", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:35567"])] + public async void DutysEdge(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + if (isText) accessory.Method.TextInfo($"与{tname}多段分摊,注意减伤", duration: 6000, true); + accessory.TTS($"与{tname}多段分摊,注意减伤", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Duty's Edge"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId(); + dp.TargetObject = @event.TargetId(); + dp.Scale = new Vector2(6, 20); + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } +} + + + + + +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 string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + public static float SourceRotation(this Event @event) + { + //return ParseHexId(@event["SourceRotation"], out var sourceRotation) ? sourceRotation : 0; + return float.TryParse(@event["SourceRotation"], out var rot) ? rot : 0; + + } + + 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 JsonConvert.DeserializeObject(@event["SourceName"]) ?? string.Empty; + } + + 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 class Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + accessory.Method.TTS(text); + } +} \ No newline at end of file diff --git a/VeeverSW/07-DawnTrail/Hells-Kier-Unreal.cs b/VeeverSW/07-DawnTrail/Hells-Kier-Unreal.cs new file mode 100644 index 0000000..5e18e7a --- /dev/null +++ b/VeeverSW/07-DawnTrail/Hells-Kier-Unreal.cs @@ -0,0 +1,1236 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using FFXIVClientStructs; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using FFXIVClientStructs.FFXIV.Client.UI; +using System.Runtime.Intrinsics.Arm; +using FFXIVClientStructs.FFXIV.Client.Game.Character; + + +namespace Veever.DawnTrail.Hells_Kier_Unreal; + +[ScriptType(name: "LV.100 朱雀幻巧战", territorys: [1272], guid: "60468283-702c-4ddb-95db-fd81409d5630", + version: "0.0.0.8", author: "Veever", note: noteStr)] + +public class Hells_Kier_Unreal +{ + const string noteStr = + """ + v0.0.0.8: + 1. 本脚本使用攻略为菓子攻略,请在打本之前调整好! 可达鸭的小队排序!!(很重要,影响指路和机制播报) + 2. 如果懒得调也不想看需要小队位置判定的指路,可以在用户设置里面关闭指路开关 + 3. 用户设置里面新加入场景标点设置(开局放置ABCD标点)(需要ACT鲶鱼精), 可能在未来弄一个不需要鲶鱼精的方法 + 4. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 鸭门。 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = true; + + [UserSetting("指路开关")] + public bool isLead { get; set; } = true; + + [UserSetting("标点开关")] + public bool isMark { get; set; } = true; + + [UserSetting("本地标点开关(打开则为本地开关,关闭则为小队)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("是否进行场地标点")] + public bool PostNamazuPrint { get; set; } = true; + + [UserSetting("鲶鱼精邮差端口设置")] + public int PostNamazuPort { get; set; } = 2019; + + [UserSetting("场地标点是否为本地标点")] + public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + + private static readonly Vector3 posA = new Vector3(100f, 0.00f, 82.70f); + private static readonly Vector3 posB = new Vector3(118.36f, 0.00f, 100f); + private static readonly Vector3 posC = new Vector3(100f, 0.00f, 118.21f); + private static readonly Vector3 posD = new Vector3(81.5f, 0.00f, 100f); + + + public int isX; + public int isIncandescent = 0; + public int isMesmerizingMelody = 0; + public int isRuthlessRefrain = 0; + public int IncandescentCount = 0; + + private readonly object IncandescentCountLock = new object(); + + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + PostWaymark(accessory); + isIncandescent = 0; + isMesmerizingMelody = 0; + isRuthlessRefrain = 0; + IncandescentCount = 0; + } + + public void PostWaymark(ScriptAccessory accessory) + { + var waymark = new NamazuHelper.Waymark(accessory); + waymark.AddWaymarkType("A", posA); + waymark.AddWaymarkType("B", posB); + waymark.AddWaymarkType("C", posC); + waymark.AddWaymarkType("D", posD); + waymark.SetJsonPayload(LocalMark, PostNamazuisLocal); + waymark.PostWaymarkCommand(PostNamazuPort); + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] + public async void debug(Event @event, ScriptAccessory accessory) + { + DebugMsg($"isIncandescent: {isIncandescent}", accessory); + } + + + #region P1 + [ScriptMethod(name: "AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43004|43015)$"])] + public void Boss1AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 2700, true); + if (isTTS) accessory.Method.TTS($"AOE"); + } + + [ScriptMethod(name: "Rout-突进", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43027"])] + public void SedimentaryDebris(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Rout"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(6f, 55); + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "008B-分散", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:008B"])] + public void ElectricExcess(Event @event, ScriptAccessory accessory) + { + lock (IncandescentCountLock) + { + DebugMsg($"isIncandescent: {isIncandescent}", accessory); + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + + var posN = new Vector3(100.01f, 0.00f, 93.97f); + var posNE = new Vector3(104.16f, 0.00f, 95.96f); + var posE = new Vector3(106.35f, 0.00f, 100.04f); + var posSE = new Vector3(104.70f, -0.00f, 104.14f); + var posS = new Vector3(100.12f, -0.00f, 106.30f); + var posSW = new Vector3(95.76f, 0.00f, 104.66f); + var posW = new Vector3(93.72f, 0.00f, 100.07f); + var posNW = new Vector3(96.21f, 0.00f, 95.33f); + + if (isIncandescent == 1) + { + DebugMsg($"IncandescentCount:{IncandescentCount}",accessory); + if (@event.TargetId() == accessory.Data.Me) + { + switch (index) + { + case 0: + if (isLead) DrawHelper.DrawDisplacement(accessory, posNE, new Vector2(1f, 1f), 5000, "TH_NE", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 1: + if (isLead) DrawHelper.DrawDisplacement(accessory, posSE, new Vector2(1f, 1f), 5000, "TH_SE", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 2: + if (isLead) DrawHelper.DrawDisplacement(accessory, posNW, new Vector2(1f, 1f), 5000, "TH_NW", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 3: + if (isLead) DrawHelper.DrawDisplacement(accessory, posSW, new Vector2(1f, 1f), 5000, "TH_SW", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 4: + if (isLead) DrawHelper.DrawDisplacement(accessory, posNE, new Vector2(1f, 1f), 5000, "DPS_NE", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 5: + if (isLead) DrawHelper.DrawDisplacement(accessory, posSE, new Vector2(1f, 1f), 5000, "DPS_SE", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 6: + if (isLead) DrawHelper.DrawDisplacement(accessory, posNW, new Vector2(1f, 1f), 5000, "DPS_NW", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 7: + if (isLead) DrawHelper.DrawDisplacement(accessory, posSW, new Vector2(1f, 1f), 5000, "DPS_SW", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + } + } + else + { + IncandescentCount++; + } + + if (IncandescentCount > 3) + { + switch (index) + { + case 0: + if (isLead) DrawHelper.DrawDisplacement(accessory, posN, new Vector2(1f, 1f), 5000, "TH_N", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 1: + if (isLead) DrawHelper.DrawDisplacement(accessory, posE, new Vector2(1f, 1f), 5000, "TH_E", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 2: + if (isLead) DrawHelper.DrawDisplacement(accessory, posW, new Vector2(1f, 1f), 5000, "TH_W", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 3: + if (isLead) DrawHelper.DrawDisplacement(accessory, posS, new Vector2(1f, 1f), 5000, "TH_S", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 4: + if (isLead) DrawHelper.DrawDisplacement(accessory, posN, new Vector2(1f, 1f), 5000, "DPS_N", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 5: + if (isLead) DrawHelper.DrawDisplacement(accessory, posE, new Vector2(1f, 1f), 5000, "DPS_E", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 6: + if (isLead) DrawHelper.DrawDisplacement(accessory, posW, new Vector2(1f, 1f), 5000, "DPS_W", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + case 7: + if (isLead) DrawHelper.DrawDisplacement(accessory, posS, new Vector2(1f, 1f), 5000, "DPS_S", color: new Vector4(1.0f, 1.0f, 0.0f, 1.0f)); + break; + } + + IncandescentCount = 0; + } + + } + else + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"分散, 不要重叠"); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "008B-分散"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + } + } + + [ScriptMethod(name: "翼宿击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43005"])] + public void FleetingSummer(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离Boss正面", duration: 2700, true); + if (isTTS) accessory.Method.TTS($"远离Boss正面"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Fleeting Summer"; + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(40); + dp.Radian = float.Pi / 2; + dp.DestoryAt = 2700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "赤热击-死刑播报", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43003"])] + public void Cremate(Event @event, ScriptAccessory accessory) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + if (@event.TargetId == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("死刑点名, 注意减伤", duration: 2500, true); + if (isTTS) accessory.Method.TTS($"死刑点名, 注意减伤"); + } + else if (index == 0 || index == 1 || index == 2 || index == 3) + { + if (isText) accessory.Method.TextInfo($"死刑点名{@event.TargetName()}", duration: 2500, true); + if (isTTS) accessory.Method.TTS($"死刑点名{@event.TargetName()}"); + } + } + + [ScriptMethod(name: "羽毛检测", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(18388|18387)$"])] + public async void FeatherCheck(Event @event, ScriptAccessory accessory) + { + // 18388 大羽 + // 18387 小羽 + var posN = new Vector3(100.00f, 0.00f, 85.00f); + var posNE = new Vector3(110.60f, 0.00f, 89.40f); + var posE = new Vector3(115.00f, 0.00f, 100.00f); + var posSE = new Vector3(110.60f, 0.00f, 110.60f); + var posS = new Vector3(100.00f, 0.00f, 115.00f); + var posSW = new Vector3(89.40f, 0.00f, 110.60f); + var posW = new Vector3(85.00f, 0.00f, 100.00f); + var posNW = new Vector3(89.40f, 0.00f, 89.40f); + + + var posN_away = new Vector3(99.51f, 0.00f, 82.63f); + var posNE_away = new Vector3(112.55f, 0.00f, 87.18f); + var posE_away = new Vector3(118.26f, 0.00f, 99.71f); + var posSE_away = new Vector3(112.78f, 0.00f, 112.55f); + var posS_away = new Vector3(99.94f, 0.00f, 118.17f); + var posSW_away = new Vector3(86.91f, 0.00f, 113.00f); + var posW_away = new Vector3(81.54f, 0.00f, 100.19f); + var posNW_away = new Vector3(86.65f, 0.00f, 87.23f); + + + var posSW_DPSX = new Vector3(93.19f, 0.00f, 106.86f); + var posSE_DPSX = new Vector3(106.59f, 0.00f, 107.08f); + var posNW_DPSX = new Vector3(93.13f, 0.00f, 92.92f); + var posNE_DPSX = new Vector3(106.87f, 0.00f, 93.15f); + + var checkPos = posN; + + if (@event.SourcePosition() != checkPos) + { + return; + } + + + if (@event.DataId() == 18387) + { + isX = 0; + } else + { + isX = 1; + } + + DebugMsg($"isx: {isX}", accessory); + + await Task.Delay(1000); + // MT: 0 + // ST: 1 + // H1: 2 + // H2: 3 + // D1: 4 + // D2: 5 + // D3: 6 + // D4: 7 + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + switch (index) + { + case 0: + case 4: + if (isX == 1) + { + // NW + DebugMsg("is NW", accessory); + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posNW, new Vector2(2f, 2f), 6000, "NW——Red", color: accessory.Data.DefaultDangerColor); + + if (index == 4) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posNW_DPSX, new Vector2(1f, 1f), 5000, "DPS_NW", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posNW, new Vector2(1f, 1f), 5000, "NW", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posNW_away, new Vector2(1f, 1f), 11000, "NW_away", delay: 13000); + await Task.Delay(13000); + if (index == 4) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + else + { + // N + DebugMsg("is N", accessory); + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posN, new Vector2(2f, 2f), 6000, "N——Red", color: accessory.Data.DefaultDangerColor); + + if (index == 4) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posNW_DPSX, new Vector2(1f, 1f), 5000, "DPS_NW", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posN, new Vector2(1f, 1f), 5000, "N", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posN_away, new Vector2(1f, 1f), 11000, "N_away", delay: 13000); + await Task.Delay(13000); + if (index == 4) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + + return; + + + case 1: + case 5: + if (isX == 1) + { + // NE + DebugMsg("is NE", accessory); + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posNE, new Vector2(2f, 2f), 6000, "NE——Red", color: accessory.Data.DefaultDangerColor); + + if (index == 5) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posNE_DPSX, new Vector2(1f, 1f), 5000, "DPS_NE", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posNE, new Vector2(1f, 1f), 5000, "NE", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posNE_away, new Vector2(1f, 1f), 11000, "NE_away", delay: 13000); + await Task.Delay(13000); + if (index == 5) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + else + { + // E + DebugMsg("is E", accessory); + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posE, new Vector2(2f, 2f), 6000, "E——Red", color: accessory.Data.DefaultDangerColor); + + if (index == 5) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posNE_DPSX, new Vector2(1f, 1f), 5000, "DPS_NE", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posE, new Vector2(1f, 1f), 5000, "E", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posE_away, new Vector2(1f, 1f), 11000, "E_away", delay: 13000); + await Task.Delay(13000); + if (index == 5) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + + return; + + case 2: + case 6: + if (isX == 1) + { + // SW + DebugMsg("is SW", accessory); + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posSW, new Vector2(2f, 2f), 6000, "SW——Red", color: accessory.Data.DefaultDangerColor); + + if (index == 6) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posSW_DPSX, new Vector2(1f, 1f), 5000, "DPS_SW", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posSW, new Vector2(1f, 1f), 5000, "SW", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posSW_away, new Vector2(1f, 1f), 11000, "SW_away", delay: 13000); + await Task.Delay(13000); + if (index == 6) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + else + { + // W + DebugMsg("is W", accessory); + + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posW, new Vector2(2f, 2f), 6000, "W——Red", color: accessory.Data.DefaultDangerColor); + + if (index == 6) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posSW_DPSX, new Vector2(1f, 1f), 5000, "DPS_SW", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posW, new Vector2(1f, 1f), 5000, "W", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posW_away, new Vector2(1f, 1f), 11000, "W_away", delay: 13000); + await Task.Delay(13000); + if (index == 6) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + + return; + + + case 3: + case 7: + if (isX == 1) + { + // SE + DebugMsg("is SE", accessory); + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posSE, new Vector2(2f, 2f), 6000, "SE——Red", color: accessory.Data.DefaultDangerColor); + + + if (index == 7) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posSE_DPSX, new Vector2(1f, 1f), 5000, "DPS_SE", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posSE, new Vector2(1f, 1f), 5000, "SE", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posSE_away, new Vector2(1f, 1f), 11000, "SE_away", delay: 13000); + await Task.Delay(13000); + if (index == 7) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + else + { + // S + DebugMsg("is S", accessory); + + if (isText) accessory.Method.TextInfo("攻击指定羽毛,不要攻击尾羽", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"攻击指定羽毛,不要攻击尾羽"); + if (isLead) DrawHelper.DrawDisplacement(accessory, posS, new Vector2(2f, 2f), 6000, "S——Red", color: accessory.Data.DefaultDangerColor); + + if (index == 7) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posSE_DPSX, new Vector2(1f, 1f), 5000, "DPS_SE", delay: 7000); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posS, new Vector2(1f, 1f), 5000, "S", delay: 7000); + } + + if (isLead) DrawHelper.DrawDisplacement(accessory, posS_away, new Vector2(1f, 1f), 11000, "S_away", delay: 13000); + await Task.Delay(13000); + if (index == 7) + { + if (isText) accessory.Method.TextInfo("引导火焰鸟至指定地点, 并迅速击杀", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"引导火焰鸟至指定地点, 并迅速击杀"); + } + } + + + return; + } + } + #endregion + + #region Switch + // ObjectEffect + // Down: Id1: 64; Id2: 32 + // Up: Id1: 8 ; Id2: 4 + // Right: Id1: 512; Id2: 256 + // Left: Id1: 4096; Id2: 2048 + + // var posTopLeft = new Vector3(94.30f, 0.00f, 94.30f); + // var posLeft = new Vector3(92.00f, 0.00f, 100.00f); + // var posBottomLeft = new Vector3(94.30f, 0.00f, 105.70f); + // var posBottom = new Vector3(100.00f, 0.00f, 108.00f); + // var posBottomRight = new Vector3(105.70f, 0.00f, 105.70f); + // var posRight = new Vector3(108.00f, 0.00f, 100.00f); + // var posTopRight = new Vector3(105.70f, 0.00f, 94.30f); + // var posTop = new Vector3(100.00f, 0.00f, 92.00f); + [ScriptMethod(name: "转场AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43009)$"])] + public void HeavyAOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("大AOE", duration: 6700, true); + if (isTTS) accessory.Method.TTS($"大AOE"); + } + #endregion + + #region Main + + [ScriptMethod(name: "引诱旋律", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43010"])] + public void MesmerizingMelody(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离中心吸引", duration: 6700, true); + if (isTTS) accessory.Method.TTS($"远离中心吸引"); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "MesmerizingMelody"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(15); + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + } + + [ScriptMethod(name: "拒绝旋律", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43011"])] + public void RuthlessRefrain(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("靠近中心击退", duration: 6700, true); + if (isTTS) accessory.Method.TTS($"靠近中心击退"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "MesmerizingMelody"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = @event.TargetPosition(); + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2,11); + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + + [ScriptMethod(name: "井宿焰", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43017"])] + public void WellofFlame(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离Boss正面", duration: 3700, true); + if (isTTS) accessory.Method.TTS($"远离Boss正面"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Well of Flame"; + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(20, 41); + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + isIncandescent = 0; + } + + [ScriptMethod(name: "00A1-素质三连分摊", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00A1"])] + public void zerozeroA1(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"00A1-素质三连分摊"; + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6f); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + var pos = new Vector3(100.01f, 0.00f, 106.51f); + var posMT = new Vector3(100.12f, 0.00f, 92.58f); + var bossid = IbcHelper.GetFirstByDataId(accessory, 18385); + if (index != 0 || index != 1) + { + if (isText) accessory.Method.TextInfo($"与{@event.TargetName()}分摊", duration: 3700, true); + if (isTTS) accessory.Method.TTS($"与{@event.TargetName()}分摊"); + if (isLead) DrawHelper.DrawDisplacement(accessory, pos, new Vector2(1, 1), 6000, "分摊闲人引导"); + } else + { + if (index == 0 && bossid?.TargetObjectId == accessory.Data.Me) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posMT, new Vector2(1, 1), 5000, "分摊MT引导"); + } else if (index == 1 && bossid?.TargetObjectId == accessory.Data.Me) + { + if (isLead) DrawHelper.DrawDisplacement(accessory, posMT, new Vector2(1, 1), 5000, "分摊ST引导"); + } + + } + + } + + [ScriptMethod(name: "鬼宿脚", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43012|43014)$"])] + public async void PhantomFlurry(Event @event, ScriptAccessory accessory) + { + if (@event.ActionId() == 43012) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + if (index == 0 || index == 1) + { + if (isText) accessory.Method.TextInfo($"鬼宿脚,准备减伤并换T,随后远离Boss正面", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"鬼宿脚,准备减伤并换T,随后远离Boss正面"); + } else + { + if (isText) accessory.Method.TextInfo($"远离Boss正面", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"远离Boss正面"); + } + } else + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"鬼宿脚"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(41f); + dp.Radian = float.Pi; + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + + + [ScriptMethod(name: "东西南北炎", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(18394|18393|18392|18391)$"])] + public unsafe void Fire(Event @event, ScriptAccessory accessory) + { + // 18391 North + // 18392 East + // 18393 South + // 18394 West + if (accessory.Data.Objects == null) + { + DebugMsg("Objects is null", accessory); + return; + } + + var battleCharas = accessory.Data.Objects.OfType(); + + uint[] playersNorth = ScanTether(@event, accessory, 18391u); + foreach (var player in playersNorth) + { + if (player != accessory.Data.Me) continue; + DebugMsg($"{player}", accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "playersNorth"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = player; + dp.Scale = new Vector2(2, 25); + dp.Rotation = float.Pi; + dp.DestoryAt = 10000; + dp.FixRotation = true; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + uint[] playersEast = ScanTether(@event, accessory, 18392u); + foreach (var player in playersEast) + { + if (player != accessory.Data.Me) continue; + DebugMsg($"{player}", accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "playersEast"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = player; + dp.Scale = new Vector2(2, 25); + dp.Rotation = float.Pi / 2; + dp.DestoryAt = 10000; + dp.FixRotation = true; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + + uint[] playersSouth = ScanTether(@event, accessory, 18393u); + foreach (var player in playersSouth) + { + if (player != accessory.Data.Me) continue; + DebugMsg($"{player}", accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "playersSouth"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = player; + dp.Scale = new Vector2(2, 25); + dp.Rotation = 0; + dp.DestoryAt = 10000; + dp.FixRotation = true; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + + + uint[] playersWest = ScanTether(@event, accessory, 18394u); + foreach (var player in playersWest) + { + if (player != accessory.Data.Me) continue; + DebugMsg($"{player}", accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "playersWest"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = player; + dp.Scale = new Vector2(2, 25); + dp.Rotation = -float.Pi / 2; + dp.DestoryAt = 10000; + dp.FixRotation = true; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + } + + [ScriptMethod(name: "红莲炎-临时", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43018"])] + public void Hotspot(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "红莲炎"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(21); + dp.Rotation = @event.SourceRotation(); + dp.ScaleMode = ScaleMode.ByTime; + dp.DestoryAt = 1000; + dp.FixRotation = true; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + // [ScriptMethod(name: "红莲炎-小鸟面向", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:18390"])] + // public void HotspotBird(Event @event, ScriptAccessory accessory) + // { + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = "红莲炎-小鸟面向"; + // dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + // dp.Owner = @event.SourceId(); + // dp.Scale = new Vector2(5); + // dp.Rotation = @event.SourceRotation() + float.Pi; + // dp.ScaleMode = ScaleMode.ByTime; + // dp.DestoryAt = 120000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp); + // } + + + [ScriptMethod(name: "灼热旋律check", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42998"])] + public void IncandescentInterlude(Event @event, ScriptAccessory accessory) + { + isIncandescent = 1; + DebugMsg("IncandescentInterlude Check", accessory); + } + + //[ScriptMethod(name: "灼热旋律结束check", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2009599"])] + //public void IncandescentInterludeFinish(Event @event, ScriptAccessory accessory) + //{ + // isIncandescent = 0; + // DebugMsg("IncandescentInterlude Finish", accessory); + //} + + + + + + + #endregion + + public static float GetStatusRemainingTime(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); + } + } + + + private unsafe uint[] ScanTether(Event evt, ScriptAccessory sa, uint id) + { + if (sa?.Data?.Objects == null) return Array.Empty(); + List dataId = [id]; + List players = []; + foreach (var fire in sa.Data.Objects.Where(x => dataId.Contains(x.DataId))) + { + if (fire?.Address == null) continue; + var targetId = ((BattleChara*)fire.Address)->Vfx.Tethers[0].TargetId.ObjectId; + players.Add(targetId); + } + DebugMsg($"players: {string.Join(", ", players)}", sa); + return players.ToArray(); + } + + public static class DrawHelper + { + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + +} + + +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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + +public static class IbcHelper +{ + public static KodakkuAssist.Data.IGameObject? GetById(ScriptAccessory accessory, uint id) + { + return accessory.Data.Objects.SearchByEntityId(id); + } + + public static KodakkuAssist.Data.IGameObject? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me); + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } + + public static IEnumerable GetParty(ScriptAccessory accessory) + { + foreach (var pid in accessory.Data.PartyList) + { + var obj = accessory.Data.Objects.SearchByEntityId(pid); + if (obj != null) yield return obj; + } + } + + public static IEnumerable GetPartyEntities(ScriptAccessory accessory) + { + return accessory.Data.Objects.Where(obj => accessory.Data.PartyList.Contains(obj.EntityId)); + } + + public static bool HasStatus(this IBattleChara ibc, uint statusId) + { + return ibc.StatusList.Any(x => x.StatusId == statusId); + } + + public static bool HasStatusAny(this IBattleChara ibc, uint[] statusIds) + { + return ibc.StatusList.Any(x => statusIds.Contains(x.StatusId)); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } +} + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + //accessory.Method.SendChat($"/e 向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } + + + + +} \ No newline at end of file diff --git a/VeeverSW/07-DawnTrail/Mistwake.cs b/VeeverSW/07-DawnTrail/Mistwake.cs new file mode 100644 index 0000000..1416b7c --- /dev/null +++ b/VeeverSW/07-DawnTrail/Mistwake.cs @@ -0,0 +1,2456 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Common.Lua; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Xml.Linq; +using static KodakkuAssist.Script.ScriptAccessory; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.DawnTrail.Mistwake; + +[ScriptType(name: Name, territorys: [1314], guid: "b7d5e223-17b8-43bf-932f-dceddf10ba1a", + version: Version, author: "Tetora & Veever", note: NoteStr, updateInfo: UpdateStr)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class Mistwake +{ + const string NoteStr = + $""" + v{Version} + ----- 请在使用前阅读注意事项 以及根据情况修改用户设置 ----- + ----- 请支持南雲鉄虎喵! 谢谢喵! ----- + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. Boss1 的 石头后范围可能不是特别精准, 请在战斗中视情况站位 + 鸭门 + ---------------------------------- + ----- Please read the notes before use and adjust user settings as needed. ----- + ----- Please support Tetora! Meow! Thank you! Meow! ----- + 1. If you need a draw or notice any issues, @ me on DC or DM me. + 2. The safe zone behind the rock during Boss1 not be perfectly precise. Please adjust your position as needed during the duty. + Duckmen. + """; + + const string UpdateStr = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.100 遗忘行路雾之迹 [Mistwake]"; + private const string Version = "0.0.0.5"; + private const string DebugVersion = "a"; + + private const bool Debugging = false; + + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + [UserSetting("是否自动使用防击退(Auto anti-knockback)")] + public bool useAntiKnockBack { get; set; } = false; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + + private readonly object CountLock = new object(); + + private bool isEdgeTTS = false; + + + public void DebugMsg(string str, ScriptAccessory sa) + { + if (!isDebug) return; + sa.Log.Debug($"[DEBUG] {str}"); + } + + private ScriptAccessory _sa = null; + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + _sa = sa; + + _ = ScriptVersionChecker.CheckVersionAsync( + sa, + "b7d5e223-17b8-43bf-932f-dceddf10ba1a", + Version, + showNotification: true + ); + + + sa.Method.ClearFrameworkUpdateAction(this); + + RefreshParams(); + } + + private void RefreshParams() + { + _Boss3FulgurousFallGuid = ""; + _Boss3Pos = -1; + _Boss3FulgurousFallCheck = -1; + } + + #region Mobs + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void destoryCancelAction(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($".*{ev.TargetId}"); + } + + [ScriptMethod(name: "雷沙尘 - Static Storm", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45204"])] + public void StaticStorm(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(8f), 120, 4000, $"StaticStorm-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Static Storm Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:45204"], userControl: false)] + public void StaticStormClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"StaticStorm-{ev.SourceId}"); + } + + [ScriptMethod(name: "落雷 - Lightning Bolt", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:46180"])] + public void LightningBolt(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Lightning Bolt-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Lightning Bolt Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:46180"], userControl: false)] + public void LightningBoltClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Lightning Bolt-{ev.SourceId}"); + } + + [ScriptMethod(name: "霹雳 - Thunderbolt", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45206"])] + public void Thunderbolt(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(12f), 120, 3700, $"Thunderbolt-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Thunderbolt Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:45206"], userControl: false)] + public void ThunderboltClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Thunderbolt-{ev.SourceId}"); + } + + [ScriptMethod(name: "眼光弹 - Knowing Gleam", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45207"])] + public void KnowingGleam(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 3700, $"Knowing Gleam-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Knowing Gleam Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:45207"], userControl: false)] + public void KnowingGleamClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Knowing Gleam-{ev.SourceId}"); + } + + [ScriptMethod(name: "大冲击波 - Megablaster", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45209"])] + public void Megablaster(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(10f), 90, 3700, $"Megablaster-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Megablaster Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:45209"], userControl: false)] + public void MegablasterClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Megablaster-{ev.SourceId}"); + } + + [ScriptMethod(name: "闪雷直击 - Thunderstrike", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45208"])] + public void Thunderstrike(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 3700, $"Thunderstrike-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Thunderstrike Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:45208"], userControl: false)] + public void ThunderstrikeClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Thunderstrike-{ev.SourceId}"); + } + #endregion + + + #region Boss1 + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "地震 - Earthquake", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43327"])] + public void Boss1MedicineField(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + + [ScriptMethod(name: "暴雷 - Thunder III", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43329"])] + public void Boss1ThunderIII(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "范围死刑,远离人群" : "AOE tankbuster — Stay away from party"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + else + { + string msg = language == Language.Chinese ? "远离范围死刑" : "Avoid AOE tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + } + + [ScriptMethod(name: "恶魔之光 - Bedeviling Light", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43330"])] + public void BedevilingLight(Event ev, ScriptAccessory sa) + { + // 18498 Small + // 18513 Large + // 18499 Medium + //sa.Data.Objects.GetByDataId(18498) + uint Boss1DataID = 18497; + List BedevilingLightObjects = new(); + List DataList = new List { 18498, 18513, 18499 }; + + //IbcHelper.GetByDataId() + + foreach (var obj in sa.Data.Objects) + { + if (DataList.Contains(obj.DataId)) + { + BedevilingLightObjects.Add(obj); + if (isDebug) sa.Log.Debug($"Dataid: {obj.DataId},pos: {obj.Position}"); + } + } + + var BossObj = IbcHelper.GetByDataId(sa, Boss1DataID).FirstOrDefault(); + foreach (var obj in BedevilingLightObjects) + { + // 未来每个石头加上 特定的角度来实现基于月环的扇形,但是先开摆(甚至感觉不用做) + DrawHelper.DrawFanPos(sa, obj.Position, BossObj.Position, float.Pi, new Vector2(15f), 35f, + 7000, $"{obj.EntityId} - BedevilingLight", color: sa.Data.DefaultSafeColor, scaleByTime: false); + /* + var offsetX = 1; + if (obj.Position.X < 83) + { + offsetX = -1; + } + + var size = 5f; + */ + + /*DrawHelper.DrawDountObjectPos(sa, obj.EntityId, BossObj.Position, float.Pi,new Vector2(15f), + MathTools.DegToRad(30f), new Vector2(5f), 7000, + $"{obj.EntityId} - BedevilingLight", scaleByTime: false, offset: new Vector3(3, 0, 2.5f), color: sa.Data.DefaultSafeColor, drawmode: DrawModeEnum.Imgui);*/ + + + /* + float dx = BossObj.Position.X - obj.Position.X; + float dz = BossObj.Position.Z - obj.Position.Z; + + float length = MathF.Sqrt(dx * dx + dz * dz); + + if (length > 0) + { + float dirX = dx / length; + float dirZ = dz / length; + + float moveDistance = -3f; + var offset = new Vector3(dirX * moveDistance, 0, dirZ * moveDistance); + + DrawHelper.DrawDountObjectPos(sa, obj.EntityId, BossObj.Position, + float.Pi, new Vector2(15f), MathTools.DegToRad(30f), + new Vector2(5f), 7000, $"{obj.EntityId} - BedevilingLight", + scaleByTime: false, offset: offset, + color: sa.Data.DefaultSafeColor, drawmode: DrawModeEnum.Imgui); + }*/ + } + + string msg = language == Language.Chinese ? "躲在石头后面" : "Hide behind the rock"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + + [ScriptMethod(name: "雷光射线(直线分摊)- Ray of Lightning", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44825"])] + public void Boss1RayofLightning(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + string msg = language == Language.Chinese ? $"与 {tname} 分摊" : $"Stack with {tname}"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + + DrawHelper.DrawRectObjectTarget(sa, ev.SourceId, ev.TargetId, new Vector2(5f, 50f), + 6200, $"Ray of Lightning - {ev.SourceId} - {ev.TargetId}", color: sa.Data.DefaultSafeColor); + } + + [ScriptMethod(name: "石化吐息(顺劈)- Petribreath", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43335"])] + public void Petribreath(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObjectNoRot(sa, ev.SourceId, new Vector2(30f), 120, 4700, $"石化吐息", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "震雷 分散提示 - Thunder II", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43331"])] + public void Boss1ThunderII(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"分散,避开石头" : $"Spread and Avoid the Rocks"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2800, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + #endregion + + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "惊雷协奏曲 - Thunderclap Concerto", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45337|45342)$"])] + public void ThunderclapConcerto(Event ev, ScriptAccessory sa) + { + var rotDanger = ev.ActionId == 45337 ? 0f.DegToRad() : 180f.DegToRad(); + var rotSafe = ev.ActionId == 45342 ? 0f.DegToRad() : 180f.DegToRad(); + + DrawHelper.DrawFanObject(sa, ev.SourceId, rotDanger, new Vector2(22f), 300, 5200, + $"Thunderclap Concerto - {ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: false); + + DrawHelper.DrawFanObject(sa, ev.SourceId, rotSafe, new Vector2(22f), 60, 5200, + $"Thunderclap Concerto - {ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + + [ScriptMethod(name: "猛毒菌 - Bio II", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45345"])] + public void BioII(Event ev, ScriptAccessory sa) + { + // 产生毒气团 DataId:19064 + if (isText) sa.Method.TextInfo($"AOE", duration: 4300, true); + if (isTTS) sa.TTS("AOE", isEdgeTTS); + } + + private readonly Queue _thunderChargeDraws = new(); + private int _drawCounter = 0; + + [ScriptMethod(name: "雷电飞驰 - Galloping Thunder", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45348"])] + public void 雷电飞驰(Event ev, ScriptAccessory sa) + { + var drawName = $"Galloping Thunder - {ev.SourceId}_{++_drawCounter}"; + _thunderChargeDraws.Enqueue(drawName); + + DrawHelper.DrawRectObjectTargetPos(sa, ev.SourceId, ev.EffectPosition, new Vector2(5), 15000, drawName, new Vector4(1, 0, 0, ColorAlpha), scalemode: ScaleMode.YByDistance); + } + + [ScriptMethod(name: "Galloping Thunder Destroy", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:45347"], userControl: false)] + public void GallopingThunderDestroy(Event ev, ScriptAccessory sa) + { + if (_thunderChargeDraws.Count > 0) + { + sa.Method.RemoveDraw(_thunderChargeDraws.Dequeue()); + } + } + + [ScriptMethod(name: "飞散 - Burst", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2536"])] + public void Burst(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(9f), 4500, $"Burst - {ev.TargetId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Burst Destroy", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:45349"],userControl: false)] + public void BurstDestroy(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Burst - {ev.TargetId}"); + } + + [ScriptMethod(name: "霹雷 - Thunder IV", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45351"])] + public void ThunderIV(Event ev, ScriptAccessory sa) + { + // 引爆剩余毒气团【45349 飞散】 + string msg = language == Language.Chinese ? "AOE, 远离剩余毒球" : "AOE, Stag Away from Thunder Ball"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4300, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + + foreach (var item in sa.Data.Objects.GetByDataId(19064)) + { + if (item is IBattleChara chara) + { + if (!IbcHelper.HasStatus(sa, chara, 0x9E8)) + { + DrawHelper.DrawCircleObject(sa, item.EntityId, new Vector2(9f), 5700, "Thunder IV", sa.Data.DefaultDangerColor, scaleByTime: false); + } + } + } + } + + [ScriptMethod(name: "暴雷(连续分摊)- Thunder III", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45353"])] + public void ThunderIII(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 7200, "Thunder III", sa.Data.DefaultSafeColor, scaleByTime: false); + } + + [ScriptMethod(name: "雷电震击 死刑 - Shockbolt", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45356"])] + public void Shockbolt(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"坦克死刑" : $"Tank Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4300, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "电光 AOE - Thunderspark", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45291"])] + public void Thunderspark(Event ev, ScriptAccessory sa) + { + string msg = $"AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4300, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + + sa.Method.ClearFrameworkUpdateAction(this); + _Boss3FulgurousFallGuid = ""; + _Boss3Pos = -1; + _Boss3FulgurousFallCheck = -1; + } + + [ScriptMethod(name: "黄金爪 死刑 - Golden Talons", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45305"])] + public void GoldenTalons(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"坦克死刑" : $"Tank Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4300, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + } + + [ScriptMethod(name: "霹雳 电球直线 - Thunderbolt", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4529[678]|4694[34])$"])] + public void Boss3Thunderbolt(Event ev, ScriptAccessory sa) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "Thunderbolt"; + dp.Owner = ev.SourceId; + dp.Color = sa.Data.DefaultDangerColor.WithW(0.6f); + dp.Scale = new (6f, 92f); + dp.DestoryAt = 5200; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + + /* + 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; + } + */ + } + + private string _Boss3FulgurousFallGuid = ""; + private float _Boss3FulgurousFallCheck = -1; + // 1 -> WE + // 2 -> NS + private int _Boss3Pos = -1; + + private void Boss3FulgurousFallFrameworkAction() + { + // center = {281, -115, -620} + // 2 west: x-- + // 2 east: x++ + var sa = _sa; + var myObj = sa.Data.MyObject; + if (myObj is null) return; + var myPos = myObj.Position; + + if (_Boss3Pos == -1) return; + + if (_Boss3Pos == 1) + { + sa.Log.Debug("in X"); + if (myPos.Z < -620) + { + sa.Log.Debug("in < -620"); + if (_Boss3FulgurousFallCheck == 0) return; + sa.Method.RemoveDraw($"Fulgurous Fall Displacement Line 1"); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Fulgurous Fall Displacement Line 0"; + dp.Scale = new(1.5f, 12); + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.Rotation = float.Pi; + dp.FixRotation = true; + dp.DestoryAt = 5700; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + _Boss3FulgurousFallCheck = 0; + } + else if (myPos.Z > -620) + { + sa.Log.Debug("in myPosZ > -620"); + if (_Boss3FulgurousFallCheck == 1) return; + sa.Method.RemoveDraw($"Fulgurous Fall Displacement Line 0"); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Fulgurous Fall Displacement Line 1"; + dp.Scale = new(1.5f, 12); + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.Rotation = 0; + dp.FixRotation = true; + dp.DestoryAt = 5700; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + _Boss3FulgurousFallCheck = 1; + } + } else if (_Boss3Pos == 2) + { + if (myPos.X < 281) + { + sa.Log.Debug("in .X < 281"); + if (_Boss3FulgurousFallCheck == 0) return; + sa.Method.RemoveDraw($"Fulgurous Fall Displacement Line 1"); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Fulgurous Fall Displacement Line 0"; + dp.Scale = new(1.5f, 12); + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.Rotation = -float.Pi / 2; + dp.FixRotation = true; + dp.DestoryAt = 5700; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + _Boss3FulgurousFallCheck = 0; + } + else if (myPos.X > 281) + { + sa.Log.Debug("in .X > 281"); + if (_Boss3FulgurousFallCheck == 1) return; + sa.Method.RemoveDraw($"Fulgurous Fall Displacement Line 0"); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Fulgurous Fall Displacement Line 1"; + dp.Scale = new(1.5f, 12); + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.Rotation = float.Pi / 2; + dp.FixRotation = true; + dp.DestoryAt = 5700; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + _Boss3FulgurousFallCheck = 1; + } + } + + } + + [ScriptMethod(name: "雷光坠击 直线击退 - Fulgurous Fall", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45301"])] + public async void FulgurousFall(Event ev, ScriptAccessory sa) + { + // 击退距离为 12m + string msg = language == Language.Chinese ? $"中间击退然后躲避直线" : $"Knockback from the center, then dodge line AOEs"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5300, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 50f), 5700, "Straight Danger", sa.Data.DefaultDangerColor); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(3f, 50f), + 5700, "Straight - Safe1", sa.Data.DefaultSafeColor, offset: new Vector3(6.5f, 0, 0)); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(3f, 50f), + 5700, "Straight - Safe2", sa.Data.DefaultSafeColor, offset: new Vector3(-6.5f, 0, 0)); + + var sPos = ev.SourcePosition; + // W & E + if ((sPos.X < 264 && sPos.Z < -617) || (sPos.X > 297 && sPos.Z < -617)) + { + _Boss3Pos = 1; + } + else if ((sPos.X < 285 && sPos.Z > -603) || (sPos.X < 285 && sPos.Z < -635)) + { + _Boss3Pos = 2; + } + + _Boss3FulgurousFallGuid = sa.Method.RegistFrameworkUpdateAction(Boss3FulgurousFallFrameworkAction); + + await Task.Delay(1500); + + if (useAntiKnockBack) sa.Method.antiKnockBack(sa); + } + + + [ScriptMethod(name: "雷击 二段直线提醒 - Electrogenetic Force", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:45302"])] + public void ElectrogeneticForce(Event ev, ScriptAccessory sa) + { + sa.Method.UnregistFrameworkUpdateAction(_Boss3FulgurousFallGuid); + _Boss3FulgurousFallGuid = ""; + _Boss3Pos = -1; + _Boss3FulgurousFallCheck = -1; + sa.Method.RemoveDraw("Fulgurous Fall Displacement Line.*"); + + string msg = language == Language.Chinese ? $"快躲开" : $"Move out!"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.TTS($"{msg}", isEdgeTTS); + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "Electrogenetic Force"; + dp.Owner = ev.SourceId; + dp.Color = new Vector4(1f, 0f, 0f, ColorAlpha); + dp.Scale = new (40f, 18f); + dp.DestoryAt = 4500; + dp.ScaleMode = ScaleMode.ByTime; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + #endregion +} + + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战物理) / "Ranged Physical DPS"(远程物理) / "Ranged Magical DPS"(远程魔法) / "Unknown" / "None" + /// 大版本更新需要维护 + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + + return playerObject.ClassJob.RowId switch + { + // Tank: 骑士、战士、暗黑骑士、绝枪战士 + 19 or 21 or 32 or 37 => "Tank", + // Healer: 白魔法师、学者、占星术士、贤者 + 24 or 28 or 33 or 40 => "Healer", + // Melee DPS: 武僧、龙骑士、忍者、武士、钐镰客、蝰蛇剑士 + 20 or 22 or 30 or 34 or 39 or 41 => "Melee DPS", + // Ranged Physical DPS: 吟游诗人、机工士、舞者 + 23 or 31 or 38 => "Ranged Physical DPS", + // Ranged Magical DPS: 黑魔法师、召唤师、赤魔法师、青魔法师、绘灵法师 + 25 or 27 or 35 or 36 or 42 => "Ranged Magical DPS", + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + public static string GetObjectName(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return "None"; + return obj.Name.ToString(); + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } + + [Flags] + public enum DrawState : uint + { + Invisibility = 0x00_00_00_02, + IsLoading = 0x00_00_08_00, + SomeNpcFlag = 0x00_00_01_00, + MaybeCulled = 0x00_00_04_00, + MaybeHiddenMinion = 0x00_00_80_00, + MaybeHiddenSummon = 0x00_80_00_00, + } + + public static unsafe DrawState* ActorDrawState(IGameObject actor) + => (DrawState*)(&((GameObject*)actor.Address)->RenderFlags); + + /// + /// 检查对象可见性(Read) + /// + /// ScriptAccessory + /// Obj need check + /// true=检查可见,false=检查不可见 + /// 如果符合检查条件返回 True + public static unsafe bool IsActorVisible(this ScriptAccessory sa, IGameObject? obj, bool checkVisible = true) + { + if (obj == null) return false; + + try + { + var state = *ActorDrawState(obj); + bool isVisible = (state & DrawState.Invisibility) == 0; + + return checkVisible ? isVisible : !isVisible; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } + + /// + /// 设置对象可见性(Write) + /// + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + if (actor == null) return; + + try + { + var statePtr = ActorDrawState(actor); + if (visible) + *statePtr &= ~DrawState.Invisibility; + else + *statePtr |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + throw; + } + } + + /// + /// Check DrawState + /// + public static unsafe bool HasDrawState(this ScriptAccessory sa, IGameObject? actor, DrawState state) + { + if (actor == null) return false; + + try + { + return (*ActorDrawState(actor) & state) != 0; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float? rotation = null, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosTarget(ScriptAccessory accessory, Vector3 pos, Vector3 targetpos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.TargetPosition = targetpos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTargetPos(ScriptAccessory accessory, ulong owner, Vector3 targetPos, Vector2 scale, int duration, string name, + Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObjectPos(ScriptAccessory accessory, ulong obj, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = obj; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isEdgeTTS) + { + accessory.Method.TTS(text); + } + + public static void antiKnockBack(this MethodAccessory method, ScriptAccessory sa) + { + // 7559 沉稳咏唱 + // 7548 亲疏自行 + var myobj = sa.Data.MyObject; + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Tank" || IbcHelper.GetPlayerRole(sa, myobj) == "Melee DPS" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Physical DPS") + { + method.UseAction(myobj.EntityId, 7548); + } + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Healer" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Magical DPS") + { + method.UseAction(myobj.EntityId, 7559); + } + } +} + +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 + +#region 脚本版本检查 +public static class ScriptVersionChecker +{ + private const string OnlineRepoUrl = "https://raw.githubusercontent.com/VeeverSW/Kodakku-Script/refs/heads/main/OnlineRepo.json"; + private static readonly HttpClient _httpClient = new HttpClient(); + + /// + /// 在线仓库脚本信息 + /// + public class OnlineScriptInfo + { + public string Name { get; set; } = ""; + public string Guid { get; set; } = ""; + public string Version { get; set; } = ""; + public string Author { get; set; } = ""; + public string Repo { get; set; } = ""; + public string DownloadUrl { get; set; } = ""; + public string Note { get; set; } = ""; + public string UpdateInfo { get; set; } = ""; + public int[] TerritoryIds { get; set; } = Array.Empty(); + } + + /// + /// 版本 + /// + public enum VersionCompareResult + { + /// 当前版本较新或相同 + UpToDate, + /// 有新版本可用 + UpdateAvailable, + /// 未找到匹配的脚本 + NotFound, + /// 检查失败 + Error + } + + /// + /// 检查脚本版本 + /// + /// ScriptAccessory + /// 脚本GUID + /// 当前版本号 + /// 是否显示通知 + /// 版本比较结果 + public static async Task<(VersionCompareResult result, OnlineScriptInfo? onlineInfo)> CheckVersionAsync( + ScriptAccessory sa, + string guid, + string currentVersion, + bool showNotification = true) + { + try + { + sa.Log.Debug($"开始检查脚本版本 (GUID: {guid}, 当前版本: {currentVersion})"); + + var response = await _httpClient.GetStringAsync(OnlineRepoUrl); + var onlineScripts = JsonConvert.DeserializeObject>(response); + + if (onlineScripts == null || onlineScripts.Count == 0) + { + sa.Log.Error("无法解析在线仓库数据"); + return (VersionCompareResult.Error, null); + } + + var onlineScript = onlineScripts.FirstOrDefault(s => + s.Guid.Equals(guid, StringComparison.OrdinalIgnoreCase)); + + if (onlineScript == null) + { + //sa.Log.Debug($"在线仓库中未找到 GUID 为 {guid} 的脚本"); + //if (showNotification) + //{ + // sa.Method.TextInfo("该脚本未在在线仓库中注册", 3000); + //} + return (VersionCompareResult.NotFound, null); + } + + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 找到在线脚本: {onlineScript.Name}, 在线版本: {onlineScript.Version}"); + + var compareResult = CompareVersions(currentVersion, onlineScript.Version); + + if (compareResult < 0) + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新 (当前: {currentVersion})"); + if (showNotification) + { + sa.Method.TextInfo( + $"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新\n当前版本: {currentVersion}", + 5000, + true); + } + return (VersionCompareResult.UpdateAvailable, onlineScript); + } + else + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 当前版本已是最新 (当前: {currentVersion}, 在线: {onlineScript.Version})"); + + return (VersionCompareResult.UpToDate, onlineScript); + } + } + catch (HttpRequestException ex) + { + //sa.Log.Error($"网络请求失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败: 网络错误", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + catch (Exception ex) + { + //sa.Log.Error($"版本检查失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + } + + /// + /// 比较版本号 + /// + /// 版本1 (例如: "0.0.0.3") + /// 版本2 (例如: "0.0.0.5") + /// 负数: version1 < version2, 0: 相等, 正数: version1 > version2 + private static int CompareVersions(string version1, string version2) + { + var v1Parts = version1.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + var v2Parts = version2.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + + int maxLength = Math.Max(v1Parts.Length, v2Parts.Length); + + for (int i = 0; i < maxLength; i++) + { + int v1Part = i < v1Parts.Length ? v1Parts[i] : 0; + int v2Part = i < v2Parts.Length ? v2Parts[i] : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } +} +#endregion diff --git a/VeeverSW/07-DawnTrail/San-d-Oria-The-Second-Walk.cs b/VeeverSW/07-DawnTrail/San-d-Oria-The-Second-Walk.cs new file mode 100644 index 0000000..ceb3be6 --- /dev/null +++ b/VeeverSW/07-DawnTrail/San-d-Oria-The-Second-Walk.cs @@ -0,0 +1,2983 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Veever.DawnTrail.San_d_Oria_The_Second_Walk; + +[ScriptType(name: Name, territorys: [1304], guid: "26a812eb-b8e2-4f31-8568-affe775bd4c6", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + + +public class San_d_Oria_The_Second_Walk +{ + const string NoteStr = + """ + v0.0.0.5 + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. Boss1可能会遇到双手直线时间偏长的问题(懒得改了影响不是很大) + 3. Boss3并没有对坦克职业的击退死刑执行自动防击退 + 4. 初版Boss2的反射激光和Boss4的复制可能会出现绘制问题(画错,不画), 请发现后携带arr在dc反馈 + 5. Boss2的地板绘制会有一点点小问题,请无视 + 6. 游戏崩溃了请务必在频道说,如果没有arr请跟我说一下大概机制和时间并提供[dalamud.log] + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + 2. Boss 1 may have an issue where the double-arm Rect AOE lasts a bit too long. + (Not a big deal so I’m too lazy to fix it.) + 3. Boss 3 does not auto-use anti-knockback for tankbuster knockbacks. + 4. In the initial version, Boss 2’s Energy Ray (reflect laser) and Boss 4’s Duplicate may have drawing issues (wrong/missing). + If you encounter this, please provide an ARR for feedback on DC. + 5. Boss 2’s floor drawing has a small issue — just ignore it. + 6. If the game crashes, please report it in the dc channel. + If no ARR is available, let me know roughly which mechanic and the time it happened, and provide your [dalamud.log]. + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.100 桑多利亚:第二巡行 [San d Oria The Second Walk]"; + private const string Version = "0.0.0.5"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static readonly Vector3 Center = new Vector3(100, 0, 100); + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + [UserSetting("是否自动使用防击退(Auto anti-knockback)")] + public bool useAntiKnockback { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + [UserSetting("目标标记开关(Target Marker toggle)")] + public bool isMark { get; set; } = true; + + [UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + private static bool _initHint = false; + + private int MoontideFontCount = 0; + public int SynchronizedStrikeCount = 0; + + private readonly object MoontideFontLock = new object(); + private readonly object CountLock = new object(); + private readonly object SynchronizedStrikeLock = new object(); + private readonly object Boss1QuadraRecordDataLock = new object(); + private readonly object TetherDataLock = new object(); + + private Dictionary tetherData = new Dictionary(); + private Dictionary Boss1QuadraRecordData = new Dictionary(); + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + MoontideFontCount = 0; + SynchronizedStrikeCount = 0; + + isSecondTankBuster = false; + duplicatePhase2 = false; + hasKnockback = false; + frameworkRegistered = false; + + sa.Method.ClearFrameworkUpdateAction(this); + Boss1QuadraRecordGuid = ""; + + tetherData.Clear(); + Boss1QuadraRecordData.Clear(); + surfaceMissiles.Clear(); + boss3TankBusterList.Clear(); + boss4TankBusterList.Clear(); + } + + + #region 小怪 + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + #region boss1前 + [ScriptMethod(name: "土烟 - Dust Cloud", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43554"])] + public void DustCloud(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(10f), 120, 2700, $"DustCloud-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Dust Cloud Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43554"], userControl: false)] + public void DustCloudClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"DustCloud-{ev.SourceId}"); + } + #endregion + + #region boss2前 + [ScriptMethod(name: "雷质横扫 - Electroswipe", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43559"])] + public void Electroswipe(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(50f), 120, 5700, $"Electroswipe-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Pressure Wave Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43559"], userControl: false)] + public void ElectroswipeClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Electroswipe-{ev.SourceId}"); + } + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void destoryCancelAction(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($".*{ev.TargetId}"); + } + #endregion + + #region boss3前 + [ScriptMethod(name: "强麻痹 - Paralyze III", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43570"])] + public void ParalyzeIII(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 5700, $"ParalyzeIII-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Paralyze III Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43570"], userControl: false)] + public void ParalyzeIIIClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"ParalyzeIII-{ev.SourceId}"); + } + + [ScriptMethod(name: "吸蚀 - Sucker", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43791"])] + public void Sucker(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(8f), 6000, $"Sucker-{ev.SourceId}", scaleByTime: false, color: new Vector4(1, 0, 0, ColorAlpha)); + if (useAntiKnockback) + { + sa.Method.UseAction(sa.Data.Me, 7559); + sa.Method.UseAction(sa.Data.Me, 7548); + } + } + + [ScriptMethod(name: "冲击之吼 - Impact Roar", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43566"])] + public void ImpactRoar(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "强放逐 & 投射 - Banish III & Catapult", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43562|43568|43567)$"])] + public void BanishIIICatapult(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"BanishIIIorCatapult-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Banish III & Catapult Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:regex:^(43562|43568|43567)$"], userControl: false)] + public void BanishIIICatapultClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"BanishIIIorCatapult-{ev.SourceId}"); + } + + + [ScriptMethod(name: "强力冲击 - Mighty Shatter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43575"])] + public void MightyShatter(Event ev, ScriptAccessory sa) + { + string msg = ""; + if (isMark) + { + msg = language == Language.Chinese ? "打断被标记目标" : "Interrupt marked target"; + } else + { + msg = language == Language.Chinese ? "打断阿尔库俄纽斯" : "Interrupt Alkyoneus"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + if (isMark) sa.Method.Mark((uint)ev.TargetId, KodakkuAssist.Module.GameOperate.MarkType.Bind1, LocalMark); + } + + [ScriptMethod(name: "强力攻击 - Power Attack", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43569"])] + public void PowerAttack(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(20f), 120, 2700, $"PowerAttack-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Dust Cloud Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43569"], userControl: false)] + public void PowerAttackClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"PowerAttack-{ev.SourceId}"); + } + + #endregion + + #endregion + + + #region Boss1 + private static IGameObject? GetBossObject(ScriptAccessory sa, uint BossDataId) + { + return sa.GetByDataId(BossDataId).FirstOrDefault(); + } + private uint westHandDataId = 18753; + private uint eastHandDataId = 18754; + + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "垒石IV-Stonega IV", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44490"])] + public void MedicineField(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "双手死亡猛击-Synchronized Smite", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44444|44443)$"])] + public void SynchronizedSmite(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(32, 60), 4700, $"SynchronizedSmite-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 30)); + } + + [ScriptMethod(name: "深红谜煞-Crimson Riddle", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45045|45044)$"])] + public void CrimsonRiddle(Event ev, ScriptAccessory sa) + { + float rot = 0; + if (ev.ActionId == 45045) rot = float.Pi; + DrawHelper.DrawFanObject(sa, ev.SourceId, rot, new Vector2(30f), 180, 4700, $"CrimsonRiddle-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + #region 四神 + [ScriptMethod(name: "Boss1_4godClear", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44414)$"], userControl: false)] + public void Boss1_4godClear(Event ev, ScriptAccessory sa) + { + sa.Method.UnregistFrameworkUpdateAction(Boss1QuadraRecordGuid); + frameworkRegistered = false; + + lock (Boss1QuadraRecordDataLock) + { + Boss1QuadraRecordData.Clear(); + } + lock (TetherDataLock) + { + tetherData.Clear(); + } + } + + #region 青龙 + // anticlock 44417 + // clock 44416 + [ScriptMethod(name: "青龙旋息-Eastwind Wheel", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44416|44417)$"])] + public void EastwindWheel(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(18, 60), 7700, $"EastwindWheelRect-{ev.SourceId}", new Vector4(0, 1, 1, ColorAlpha)); + + if (ev.ActionId == 44417) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, float.Pi / 4, new Vector2(65f), 90, 7700, $"EastwindWheelFan-{ev.SourceId}", new Vector4(0, 1, 1, ColorAlpha), offset: new Vector3(9, 0, 0)); + } else if (ev.ActionId == 44416) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, -float.Pi / 4, new Vector2(65f), 90, 7700, $"EastwindWheelFan-{ev.SourceId}", new Vector4(0, 1, 1, ColorAlpha), offset: new Vector3(-9, 0, 0)); + } + } + + #endregion + + #region 白虎 + // 44432 circle efpos + // 44431 rect boss + [ScriptMethod(name: "白虎腾跃-Gloaming Gleam", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44431)$"])] + public void GloamingGleam(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(12, 50), 3000, $"GloamingGleam-{ev.SourceId}", new Vector4(1, 1, 0, ColorAlpha)); + } + + [ScriptMethod(name: "剔骨獠牙-Razor Fang", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44432)$"])] + public void RazorFang(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(20f), 4500, $"RazorFang-{ev.SourceId}", new Vector4(1, 1, 0, ColorAlpha), scaleByTime: false); + } + #endregion + + #region 玄武 + [ScriptMethod(name: "喷水流-MoontideFont", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44421|44422)$"])] + public void MoontideFont(Event ev, ScriptAccessory sa) + { + lock (MoontideFontLock) + { + MoontideFontCount++; + DebugMsg($"MoontideFontCount: {MoontideFontCount}", sa); + } + + int duration = int.TryParse(ev["DurationMilliseconds"]?.ToString(), out var d) ? d : 8000; + var delay = 0; + + DebugMsg($"time: {duration}", sa); + + if (MoontideFontCount >= 10) + { + delay = 3000; + duration -= 3000; + } + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(9f), duration, $"MoontideFont-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: delay); + } + + [ScriptMethod(name: "玄武突进-Midwinter March", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44337)$"])] + public async void MidwinterMarch(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "靠近危险区域准备穿进月环" : "Stay near danger zone, then move in"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(12f), 6700, $"MidwinterMarch-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + await Task.Delay(7000); + + string msg1 = language == Language.Chinese ? "进入月环" : "Move in (Dynamo)"; + if (isText) sa.Method.TextInfo($"{msg1}", duration: 3500, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(12f), 3500, $"MidwinterMarchDountSafe-{ev.SourceId}", sa.Data.DefaultSafeColor, scaleByTime: false); + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(60f), new Vector2(12f), 3500, $"MidwinterMarchDount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "死亡旋转-Dead Wringer", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44439)$"])] + public void DeadWringer(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "进入月环" : "Move in (Dynamo)"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(14f), 4700, $"DeadWringerDountSafe-{ev.SourceId}", sa.Data.DefaultSafeColor, scaleByTime: false); + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(60f), new Vector2(14f), 4700, $"DeadWringerDount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + + #endregion + + #region 朱雀 + [ScriptMethod(name: "延烧-Arm of Purgatory", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44796|44794)$"])] + public void ArmofPurgatory(Event ev, ScriptAccessory sa) + { + if (ev.ActionId == 44796) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(15f), 5200, $"ArmofPurgatory-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + else + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(15f), 10700, $"ArmofPurgatory-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + } + + [ScriptMethod(name: "朱雀翔空-Vermilion Flight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44795)$"])] + public void VermilionFlight(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20, 60), 7700, $"VermilionFlight-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + + + #endregion + + #endregion + + + [ScriptMethod(name: "连线记录0156", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(0156)$"], userControl: false)] + public async void tetherRecorder0156(Event ev, ScriptAccessory sa) + { + DebugMsg($"0156 record {ev.SourceId}", sa); + lock (TetherDataLock) + { + tetherData[ev.Id] = (ev.SourceId, ev.SourcePosition); + } + + await Task.Delay(4700); + lock (TetherDataLock) + { + tetherData.Clear(); + } + } + + + [ScriptMethod(name: "右手猛击-Striking Right", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44435|44462)$"])] + public void StrikingRight(Event ev, ScriptAccessory sa) + { + Vector3 tetherPosition; + lock (TetherDataLock) + { + if (!tetherData.ContainsKey(0156)) + { + DebugMsg($"Tether data for 0156 not found", sa); + return; + } + tetherPosition = tetherData[0156].Item2; + } + + if (ev.ActionId == 44435) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, $"StrikingRight-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + + DrawHelper.DrawCircle(sa, tetherPosition, new Vector2(30f), 4700, $"StrikingRightBig-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + else + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, $"StrikingRight-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 3000); + + DrawHelper.DrawCircle(sa, tetherPosition, new Vector2(30f), 4700, $"StrikingRightBig-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 3000); + } + + } + + [ScriptMethod(name: "左手猛击-Striking Left", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44436|44463)$"])] + public void StrikingLeft(Event ev, ScriptAccessory sa) + { + Vector3 tetherPosition; + lock (TetherDataLock) + { + if (!tetherData.ContainsKey(0156)) + { + DebugMsg($"Tether data for 0156 not found", sa); + return; + } + tetherPosition = tetherData[0156].Item2; + } + + if (ev.ActionId == 44436) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, $"StrikingLeft-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + + DrawHelper.DrawCircle(sa, tetherPosition, new Vector2(30f), 4700, $"StrikingLeftBig-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + else + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, $"StrikingLeft-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 3000); + + DrawHelper.DrawCircle(sa, tetherPosition, new Vector2(30f), 4700, $"StrikingLeftBig-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 3000); + } + + } + + [ScriptMethod(name: "右手死亡猛击-Striking Right Large", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44441)$"])] + public void SmitingRightLarge(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(30f), 4700, $"SmitingRightLarge-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + + + [ScriptMethod(name: "旋转连击-Double Wringer", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44445)$"])] + public void DoubleWringer(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(14f), 9700, $"SmitingRightLarge-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + + + [ScriptMethod(name: "双手猛击-Synchronized Strike", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44464)$"])] + public async void SynchronizedStrike(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10, 60), 8200, $"SynchronizedStrike-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), offset: new Vector3(0, 0, 30)); + lock (SynchronizedStrikeLock) + { + SynchronizedStrikeCount++; + DebugMsg($"SynchronizedStrikeCount: {SynchronizedStrikeCount}", sa); + } + + if (SynchronizedStrikeCount == 2) + { + unsafe + { + var x = AgentMap.Instance()->CurrentTerritoryId; + } + + DebugMsg($"SynchronizedStrikeCount == 2", sa); + var westHand = GetBossObject(sa, westHandDataId); + var eastHand = GetBossObject(sa, eastHandDataId); + if (westHand == null || eastHand == null) return; + + DrawHelper.DrawRectObjectNoTarget(sa, westHand.EntityId, new Vector2(32, 60), 4700, $"SynchronizedStrike2-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 30), delay: 12000); + DrawHelper.DrawRectObjectNoTarget(sa, eastHand.EntityId, new Vector2(32, 60), 4700, $"SynchronizedStrike2-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 30), delay: 12000); + await Task.Delay(5000); + SynchronizedStrikeCount = 0; + } + + } + + private string Boss1QuadraRecordGuid = ""; + public bool frameworkRegistered = false; + // actionId, (sid, efpos, count) + + [ScriptMethod(name: "Boss1四连记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44433)$"])] + public void Boss1QuRecord(Event ev, ScriptAccessory sa) + { + lock (Boss1QuadraRecordDataLock) + { + Boss1QuadraRecordData.Clear(); + } + + if (!string.IsNullOrEmpty(Boss1QuadraRecordGuid)) + { + sa.Method.UnregistFrameworkUpdateAction(Boss1QuadraRecordGuid); + } + + DebugMsg($"Register new framework for Boss1QuRecord", sa); + sa.Log.Debug($"Register new framework for Boss1QuRecord"); + Boss1QuadraRecordGuid = sa.Method.RegistFrameworkUpdateAction(() => + { + try + { + Dictionary localData; + lock (Boss1QuadraRecordDataLock) + { + if (Boss1QuadraRecordData.Count != 2) return; + localData = new Dictionary(Boss1QuadraRecordData); + Boss1QuadraRecordData.Clear(); + } + + var westHand = GetBossObject(sa, westHandDataId); + var eastHand = GetBossObject(sa, eastHandDataId); + if (westHand == null || eastHand == null) return; + + foreach (var kv in localData) + { + switch (kv.Value.Item3) + { + case 0: + HandleBoss1Quadra(sa, kv.Key, kv.Value.Item1, kv.Value.Item2, westHand, eastHand, 0); + break; + case 1: + HandleBoss1Quadra(sa, kv.Key, kv.Value.Item1, kv.Value.Item2, westHand, eastHand, 6500); + break; + } + } + } + catch (Exception ex) + { + sa.Log.Error($"Framework callback error: {ex.Message}"); + lock (Boss1QuadraRecordDataLock) + { + Boss1QuadraRecordData.Clear(); + } + } + }); + frameworkRegistered = true; + } + + + public void HandleBoss1Quadra(ScriptAccessory sa, uint actionId, ulong sid, Vector3 efpos, IGameObject? westHand, IGameObject? eastHand, int delay) + { + // 44446 - westhand 30f + efpos 10f circle draw + // 44461 - Boss Chariot with later Dynamo + // 44464 - single rect stick with later large rect with 2 hands + DebugMsg($"HandleBoss1Quadra In, ActionId: {actionId}", sa); + sa.Log.Debug($"HandleBoss1Quadra In, ActionId: {actionId}"); + switch (actionId) + { + // 44446 + case 44462: + DebugMsg($"44462 case", sa); + sa.Log.Debug($"HandleBoss1Quadra In, ActionId: {actionId}"); + DrawHelper.DrawCircle(sa, efpos, new Vector2(10f), 4700, $"44462s-{efpos}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: delay); + + if (westHand != null) + { + DrawHelper.DrawCircle(sa, westHand.Position, new Vector2(30f), 6500, $"44462L-{westHand.EntityId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: delay); + } + + break; + // 44447 + case 44463: + DebugMsg($"44463 case", sa); + sa.Log.Debug($"HandleBoss1Quadra In, ActionId: {actionId}"); + DrawHelper.DrawCircle(sa, efpos, new Vector2(10f), 4700, $"44463s-{efpos}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: delay); + + if (eastHand != null) + { + DrawHelper.DrawCircle(sa, eastHand.Position, new Vector2(30f), 6500, $"44463L-{eastHand.EntityId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: delay); + } + + break; + case 44461: + DebugMsg($"44461 case", sa); + sa.Log.Debug($"HandleBoss1Quadra In, ActionId: {actionId}"); + DrawHelper.DrawCircle(sa, efpos, new Vector2(14f), 5000, $"44461Danger-{sid}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: delay); + + DrawHelper.DrawCircle(sa, efpos, new Vector2(14f), 4700, $"44461safe-{sid}", sa.Data.DefaultSafeColor, scaleByTime: false, delay: 5000 + delay); + DrawHelper.DrawDount(sa, efpos, new Vector2(60f), new Vector2(14f), 4700, $"44461dount-{sid}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 5000 + delay); + + break; + //case 44464: + // DebugMsg($"44464 case", sa); + // sa.Log.Debug($"44464 case"); + // //DrawHelper.DrawRectObjectNoTarget(sa, sid, new Vector2(10, 60), 8000, $"44464stick-{efpos}", new Vector4(1, 0, 0, 1), offset: new Vector3(0, 0, 30), delay: delay); + // if (westHand != null && eastHand != null) + // { + // RemoveSynchronizedStrike2(sa); + // DrawHelper.DrawRectPosNoTarget(sa, westHand.Position, new Vector2(32, 60), 4700, $"44464west", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 30), delay: 12000 + delay); + // DrawHelper.DrawRectPosNoTarget(sa, eastHand.Position, new Vector2(32, 60), 4700, $"44464east", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 30), delay: 12000 + delay); + + // } + // break; + } + } + + public async void RemoveSynchronizedStrike2(ScriptAccessory sa) + { + await Task.Delay(1000); + sa.Method.RemoveDraw($"SynchronizedStrike2.*"); + } + + [ScriptMethod(name: "Boss1ActionMonitor", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44462|44463|44464|44461)$"], userControl: false, suppress: 2000)] + public void Boss1ActionMonitor(Event ev, ScriptAccessory sa) + { + DebugMsg($"RecordAction {ev.ActionId}", sa); + sa.Log.Debug($"RecordAction {ev.ActionId}"); + + lock (Boss1QuadraRecordDataLock) + { + var item3 = Boss1QuadraRecordData.Count; + + if (!Boss1QuadraRecordData.ContainsKey(ev.ActionId)) + { + Boss1QuadraRecordData.Add(ev.ActionId, (ev.SourceId, ev.EffectPosition, item3)); + DebugMsg($"Boss1QuadraRecordData.Add post: ActionId:{ev.ActionId}; SourceId: {ev.SourceId}; EffectPosition: {ev.EffectPosition}, item3: {item3}", sa); + sa.Log.Debug($"Boss1QuadraRecordData.Add post: ActionId:{ev.ActionId}; SourceId: {ev.SourceId}; EffectPosition: {ev.EffectPosition}, item3: {item3}"); + } + else + { + DebugMsg($"ActionId {ev.ActionId} already exists in Boss1QuadraRecordData, skipping", sa); + sa.Log.Debug($"ActionId {ev.ActionId} already exists in Boss1QuadraRecordData, skipping"); + } + } + } + + #region 沙地 + [ScriptMethod(name: "致命拥抱-Deadly Hold", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44466)$"])] + public void DeadlyHold(Event ev, ScriptAccessory sa) + { + sa.Method.UnregistFrameworkUpdateAction(Boss1QuadraRecordGuid); + frameworkRegistered = false; + + lock (Boss1QuadraRecordDataLock) + { + Boss1QuadraRecordData.Clear(); + } + lock (TetherDataLock) + { + tetherData.Clear(); + } + + bool isTank = ExtensionMethods.IsTank(sa.Data.MyObject); + if (isTank) + { + DebugMsg($"isTank: {isTank}", sa); + string msg = language == Language.Chinese ? "坦克踩塔" : "Tanks take tower"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 8000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + DebugMsg($"isTank: {isTank}", sa); + string msg = language == Language.Chinese ? "攻击手臂" : "Attack Arms"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 8000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + #endregion + [ScriptMethod(name: "Boss1Clear", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:regex:^(18754)$"], userControl: false)] + public void Boss1Clear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw(".*"); + MoontideFontCount = 0; + SynchronizedStrikeCount = 0; + + sa.Method.ClearFrameworkUpdateAction(this); + Boss1QuadraRecordGuid = ""; + frameworkRegistered = false; + + lock (Boss1QuadraRecordDataLock) + { + Boss1QuadraRecordData.Clear(); + } + lock (TetherDataLock) + { + tetherData.Clear(); + } + lock (SurfaceMissileLock) + { + surfaceMissiles.Clear(); + } + } + #endregion + + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44331"])] + public void boss2AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44305"])] + public void boss2Tankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名, 注意减伤" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + // TODO: Check correctness + [ScriptMethod(name: "能量射线 - Energy Ray", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44338"])] + public void EnergyRay(Event ev, ScriptAccessory sa) + { + var casterPos = ev.SourcePosition; + var rotation = ev.SourceRotation; + + // initial rect + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(16f, 40f), 4700, $"EnergyRay-Initial-{ev.SourceId}", sa.Data.DefaultDangerColor); + + // initial origin + var initialPosMap = new Dictionary + { + { new Vector3(755f, 320, 816f), new int[] { 0, 1 } }, + { new Vector3(755f, 320, 800f), new int[] { 2, 3 } }, + { new Vector3(820f, 380, 784f), new int[] { 4 } }, + { new Vector3(820f, 380, 816f), new int[] { 5 } }, + { new Vector3(755f, 320, 784f), new int[] { 6, 7 } } + }; + + // reflect pos and angle + var aoeMap = new (Vector3 firstReflect, int angleFirst, Vector3 secondReflect, int angleSecond)[] + { + (new Vector3(725f, 320, 816f), 2, new Vector3(725f, 320, 784f), 1), // checked + (new Vector3(745f, 320, 816f), 2, new Vector3(745f, 320, 800f), 3), // checked + (new Vector3(745f, 320, 800f), 0, new Vector3(745f, 320, 816f), 3), // checked + (new Vector3(725f, 320, 800f), 2, new Vector3(725f, 320, 784f), 1), + (new Vector3(810f, 380f, 784f), 0, Vector3.Zero, 0), // checked + (new Vector3(810f, 380f, 816f), 2, Vector3.Zero, 0), // checked + (new Vector3(745f, 320, 784f), 0, new Vector3(745f, 320, 816f), 3), // checked + (new Vector3(725f, 320, 784f), 0, new Vector3(725f, 320, 800f), 1) // checked + }; + + // Dir (0 = 东, 1 = 北, 2 = 西, 3 = 南) + var cardinalAngles = new float[] { 0f, float.Pi / 2, float.Pi, -float.Pi / 2 }; + + + var manaScreens = sa.Data.Objects.Where(obj => obj.DataId == 0x1EBE8B || obj.DataId == 0x1EBE8C).ToList(); + + if (manaScreens.Count == 0) return; + + DebugMsg($"Found Screen, count: {manaScreens.Count}", sa); + + foreach (var posMapping in initialPosMap) + { + if (Vector3.Distance(casterPos, posMapping.Key) < 2f) + { + DebugMsg($"Caster position matched: {posMapping.Key}", sa); + var indices = posMapping.Value; + DebugMsg($"Possible indices: {string.Join(", ", indices)}", sa); + foreach (var index in indices) + { + var aoe = aoeMap[index]; + + // Check Screen existence + var matchingScreen = manaScreens.FirstOrDefault(screen => + Vector3.Distance(screen.Position, aoe.firstReflect) < 2f); + + if (matchingScreen != null) + { + var firstAngle = cardinalAngles[aoe.angleFirst]; + var dp1 = sa.Data.GetDefaultDrawProperties(); + dp1.Name = $"EnergyRay-Reflect1-{index}"; + dp1.Color = sa.Data.DefaultDangerColor; + dp1.Position = aoe.firstReflect; + dp1.Scale = new Vector2(20f, 48f); + dp1.Rotation = firstAngle; + dp1.Delay = 700; + dp1.DestoryAt = 4700; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp1); + DebugMsg($"First reflection drawn at {aoe.firstReflect} with angle {firstAngle} rad ({firstAngle * 180 / float.Pi} deg)", sa); + + if (aoe.secondReflect != Vector3.Zero) + { + var secondAngle = cardinalAngles[aoe.angleSecond]; + var dp2 = sa.Data.GetDefaultDrawProperties(); + dp2.Name = $"EnergyRay-Reflect2-{index}"; + dp2.Color = sa.Data.DefaultDangerColor; + dp2.Position = aoe.secondReflect; + dp2.Scale = new Vector2(20f, 40f); + dp2.Rotation = secondAngle; + dp2.Delay = 1200; + dp2.DestoryAt = 4700; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp2); + DebugMsg($"Second reflection drawn at {aoe.secondReflect} with angle {secondAngle} rad ({secondAngle * 180 / float.Pi} deg)", sa); + } + return; + } + } + } + } + } + + [ScriptMethod(name: "能量射线清理 - Energy Ray Clean", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44338|44339|44340|44341|44342)$"], userControl: false)] + public void EnergyRayClean(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"EnergyRay-.*-{ev.SourceId}"); + } + + [ScriptMethod(name: "齐射记录-FireRecorder", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44327|44325)$"])] + public void FireRecorder(Event ev, ScriptAccessory sa) + { + // 44327 后前齐射 + // 44325 前后齐射 + string msg = ""; + if (ev.ActionId == 44327) + { + msg = language == Language.Chinese ? "先去前面再去后面" : "Front then back!"; + } else + { + msg = language == Language.Chinese ? "先去后面再去前面" : "Back then Front!"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "欧米茄冲击波 - Omega Blaster", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44329|44330)$"])] + public void OmegaBlaster(Event ev, ScriptAccessory sa) + { + // 44329 first + // 44330 second + //var rot = 0f; + //if (ev.ActionId == 44329) + //DrawHelper.DrawFanObject(sa, ev.SourceId, ev.SourceRotation + (50 * float.Pi), new Vector2(50f), 180, 6500, $"OmegaBlaster-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + //DrawHelper.DrawFanObject(sa, ev.SourceId, new Vector2(50f), 180, 6500, $"OmegaBlaster-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + int delay = 0; + int destory = 6500; + if (ev.ActionId == 44330) + { + delay = 6500; + destory = 2500; + } + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"OmegaBlaster-{ev.SourceId}"; + dp.Color = new Vector4(0, 1, 1, ColorAlpha); + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(50f); + dp.Radian = 180 * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = destory; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "冲撞 - Crash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44295"])] + public void Crash(Event ev, ScriptAccessory sa) + { + var pos = ev.SourcePosition; + bool isWest = false; + + if (pos.X < 781) + { + isWest = true; + } + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(24, 40), 10200, $"Crash-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha)); + + if (isWest) + { + //var targetPosition = mypos.Value with { X = mypos.Value.X + 5 }; + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "CrashDisplacementWest"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + //dp.TargetPosition = targetPosition; + dp.Rotation = float.Pi / 2; + dp.Scale = new Vector2(2, 25f); + dp.FixRotation = true; + dp.DestoryAt = 10200; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } else if (!isWest) + { + //var targetPosition = mypos.Value with { X = mypos.Value.X - 5 }; + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "CrashDisplacementEast"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + //dp.TargetPosition = targetPosition; + dp.Rotation = -float.Pi / 2; + dp.Scale = new Vector2(2, 25f); + dp.FixRotation = false; + dp.DestoryAt = 10200; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + surfaceMissiles.Clear(); + } + + private List<(Vector3 position, ulong tid)> surfaceMissiles = new(); + private readonly object SurfaceMissileLock = new object(); + + //Todo: 地板有问题 + [ScriptMethod(name: "地板记录 - Surface Missile Record", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0268"])] + public void surfaceMissileRecord(Event ev, ScriptAccessory sa) + { + var targetObj = IbcHelper.GetById(sa, ev.TargetId); + if (targetObj == null) return; + var pos = targetObj.Position; + if (pos == Vector3.Zero) return; + + DebugMsg($"Surface Missile Record {ev.TargetId}, pos: {pos}", sa); + sa.Log.Debug($"Surface Missile Record {ev.TargetId}, pos: {pos}"); + lock (SurfaceMissileLock) + { + surfaceMissiles.Add((pos.Quantized(), ev.TargetId)); + DebugMsg($"surfaceMissiles Count: {surfaceMissiles.Count}", sa); + sa.Log.Debug($"surfaceMissiles Count: {surfaceMissiles.Count}"); + } + + if (surfaceMissiles.Count == 12) + { + DebugMsg($"----------surfaceMissiles == 12--------------", sa); + sa.Log.Debug($"----------surfaceMissiles == 12--------------"); + + int index = 1; + + foreach (var t in surfaceMissiles) + { + Vector3 position = t.position; + ulong targetId = t.tid; + + DebugMsg($"index: {index}; pos: {position}; tid: {targetId}", sa); + sa.Log.Debug($"index: {index}; pos: {position}; tid: {targetId}"); + index++; + } + + var delay = 0; + var destory = 0; + + for (int i = 0; i < surfaceMissiles.Count; i++) + { + var (position, tid) = surfaceMissiles[i]; + + if (i < 4) + { + destory = 2800; + } + if (i >= 4 && i < 8) + { + delay = 2800; + destory = 2800; + } + if (i >= 8) + { + delay = 5600; + destory = 3000; + } + + DrawHelper.DrawRectObjectNoTarget(sa, tid, new Vector2(20, 12), destory, $"{i}: surfaceMissiles - delay:{delay} - destory:{destory}", offset: new Vector3(0, 0, 6f), delay: delay); + } + } + } + + [ScriptMethod(name: "制导导弹 - Guided Missile", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44324"])] + public void GuidedMissile(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 1000, $"GuidedMissile-{ev.EffectPosition}", scaleByTime: false, color: new Vector4(1, 0, 0, ColorAlpha)); + } + + [ScriptMethod(name: "多重导弹 - Multi-missile", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45037|45036)$"])] + public void Multimissile(Event ev, ScriptAccessory sa) + { + // 45037 small 3700 + // 45036 large 3800 + var destory = 3700; + var size = 6f; + if (ev.ActionId == 45036) + { + destory = 3800; + size = 10f; + } + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(size), destory, $"Multimissile-{ev.EffectPosition}"); + } + + [ScriptMethod(name: "堡垒围攻 - Citadel Siege", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44312"])] + public void CitadelSiege(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 48f), 4700, $"Citadel Siege-{ev.EffectPosition}", color: new Vector4(1, 0, 0, ColorAlpha), scalemode: ScaleMode.ByTime); + } + + [ScriptMethod(name: "化学炸弹 - Chemical Bomb", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44303"])] + public void ChemicalBomb(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(20f), 4700, $"Chemical Bomb-{ev.EffectPosition}", color: new Vector4(1, 0, 0, ColorAlpha), delay: 2700, scaleByTime: false); + } + + + [ScriptMethod(name: "Boss2 Clear & reset", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:18759"], userControl: false, suppress: 3000)] + public void Boss2Clear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw(".*"); + lock (SurfaceMissileLock) + { + surfaceMissiles.Clear(); + } + } + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:(44221|44212)"])] + public void boss3AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (ev.ActionId == 44212) + { + msg = language == Language.Chinese ? "大AOE" : "Heavy AOE"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "决战之地 - Proving Ground", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:45065"])] + public void ProvingGround(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "远离Boss脚下" : "Don't stand under boss"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + DrawHelper.DrawCircle(sa, ev.SourcePosition, new Vector2(5f), 2700, $"ProvingGround-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + [ScriptMethod(name: "魔法剑 - Elemental Blade", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44(19[1-9]|20[0-2]|179|18[0-9]|190))$"])] + public void ElementalBlade(Event ev, ScriptAccessory sa) + { + // Rect width length + // 44191 火 5 80 + // 44192 土 5 80 + // 44193 水 5 80 + // 44194 冰 5 80 + // 44195 雷 5 80 + // 44196 风 5 80 + // ----------------------------------- + // 44197 火 20 80 + // 44198 土 20 80 + // 44199 水 20 80 + // 44200 冰 20 80 + // 44201 雷 20 80 + // 44202 风 20 80 + DebugMsg($"Action Id: {ev.ActionId}", sa); + var width = 5f; + + if (ev.ActionId >= 44197) + { + width = 20f; + } + + if (ev.ActionId >= 44191 && ev.ActionId <= 44202) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(width, 80f), 8700, $"ElementalBlade-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha)); + } + + // Fan Angle + // 44179 火 20 + // 44180 土 20 + // 44181 水 20 + // 44182 冰 20 + // 44183 雷 20 + // 44184 风 20 + // ----------------------------------- + // 44185 火 100 + // 44186 土 100 + // 44187 水 100 + // 44188 冰 100 + // 44189 雷 100 + // 44190 风 100 + var angle = 20f; + + if (ev.ActionId >= 44185) + { + angle = 100f; + } + + if (ev.ActionId >= 44179 && ev.ActionId <= 44190) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"ElementalBladeFan-{ev.SourceId}"; + dp.Color = new Vector4(1, 0, 0, ColorAlpha); + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(45f); + dp.Radian = angle * (float.Pi / 180); + dp.DestoryAt = 8700; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + + public bool isSecondTankBuster = false; + List boss3TankBusterList = new List(); + private readonly object boss3TankBusterLock = new object(); + [ScriptMethod(name: "坦克击退死刑提示 - Tank knock-back buster Notify", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0265"])] + public void boss3TankBuster(Event ev, ScriptAccessory sa) + { + lock (boss3TankBusterLock) + { + boss3TankBusterList.Add(ev.TargetId); + DebugMsg($"boss3TankBusterList Count: {boss3TankBusterList.Count}, isSecondTankBuster: {isSecondTankBuster}", sa); + if (boss3TankBusterList.Count == 3) + { + DebugMsg($"Contains me :{boss3TankBusterList.Contains((ulong)sa.Data.Me)}", sa); + sa.Log.Debug($"me: {sa.Data.Me}, list1: {boss3TankBusterList[0]}; List2: {boss3TankBusterList[1]}, List3: {boss3TankBusterList[2]}"); + + string msg = ""; + if (boss3TankBusterList.Contains((ulong)sa.Data.Me)) + { + msg = language == Language.Chinese ? "击退死刑, 注意减伤" : "Tankbuster with knockback, Use mits"; + } + else + { + msg = language == Language.Chinese ? "远离范围AOE死刑" : "AOE Tankbuster — Stay Away"; + } + if (isText) sa.Method.TextInfo($"{msg}", duration: 6900, true); + if (isTTS) sa.Method.TTS($"{msg}"); + boss3TankBusterList.Clear(); + } + } + + + DrawHelper.DrawRectObjectTarget(sa, ev.SourceId, ev.TargetId, new Vector2(10f, 40f), 6900, $"TankBusterKnockback-{ev.TargetId}", color: new Vector4(1, 0, 0, ColorAlpha)); + + if (ev.TargetId == sa.Data.Me && isLead) + { + var knockbackDistance = 20f; + if (isSecondTankBuster) knockbackDistance = 30f; + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "boss3TankBuster Knockback"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetObject = ev.SourceId; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, knockbackDistance); + dp.DestoryAt = 6900; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + + [ScriptMethod(name: "崇高之剑 - Sublime Estoc", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:9352"])] + public void SublimeEstoc(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(5f, 40f), 4500, $"SublimeEstoc-{ev.SourceId}", color: new Vector4(1, 1, 0, ColorAlpha)); + } + + [ScriptMethod(name: "回环连斩 - Great Wheel", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44207|44205|44206)$"])] + public void GreatWheel(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "钢铁远离" : "Chariot (Out)"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 1000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(10f), 2700, $"GreatWheel-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha)); + } + + [ScriptMethod(name: "回环连斩正面 - Great Wheel Front", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44209"])] + public void GreatWheelFan(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(40f), 180, 5500, $"GreatWheelFan-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + [ScriptMethod(name: "属性反应 - Elemental Resonance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44216"])] + public void ElementalResonance(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(18f), 6700, $"ElementalResonance-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + [ScriptMethod(name: "幻光剑 - Illumed Estoc", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44218"])] + public void IllumedEstoc(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(13f, 120f), 7700, $"IllumedEstoc-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), offset: new Vector3(0, 0, 30)); + } + + [ScriptMethod(name: "盾牌猛击 - Shield Bash", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44222"])] + public void ShieldBash(Event ev, ScriptAccessory sa) + { + isSecondTankBuster = true; + string msg = language == Language.Chinese ? "击退至安全通道" : "Knockback to safe lane"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 6700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + List safePos = new List + { + new Vector3(-199.95f, -900.00f, 144.75f), + new Vector3(-195.50f, -900.00f, 152.50f), + new Vector3(-205.20f, -900.00f, 152.88f) + }; + + foreach (var pos in safePos) + { + DrawHelper.DrawCircle(sa, pos, new Vector2(2f), 6700, $"ShieldBashSafe-{pos}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "ShieldBash Knockback"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetObject = ev.SourceId; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, 30f); + dp.DestoryAt = 6700; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "放逐IV - Empyreal Banish IV", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44224"])] + public void EmpyrealBanishIV(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + + if (ev.TargetId == sa.Data.Me) + { + msg = language == Language.Chinese ? "分摊点名" : "Stack"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(5f), 4700, $"EmpyrealBanishIV-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + + #endregion + #region Boss4 + [ScriptMethod(name: "---- Boss4 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss4(Event ev, ScriptAccessory sa) + { + } + + + + List boss4TankBusterList = new List(); + private readonly object boss4TankBusterLock = new object(); + [ScriptMethod(name: "坦克击退死刑提示 - Tank knock-back buster Notify", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0158"])] + public void boss4TankBuster(Event ev, ScriptAccessory sa) + { + lock (boss4TankBusterLock) + { + boss4TankBusterList.Add(ev.TargetId); + DebugMsg($"boss4TankBusterList Count: {boss4TankBusterList.Count}", sa); + if (boss4TankBusterList.Count == 3) + { + DebugMsg($"Contains me :{boss4TankBusterList.Contains((ulong)sa.Data.Me)}", sa); + if (isDebug) sa.Log.Debug($"me: {sa.Data.Me}, list1: {boss4TankBusterList[0]}; List2: {boss4TankBusterList[1]}, List3: {boss4TankBusterList[2]}"); + + string msg = ""; + if (boss4TankBusterList.Contains((ulong)sa.Data.Me)) + { + msg = language == Language.Chinese ? "范围死刑, 注意减伤" : "AOE Tankbuster, Use mits"; + } + else + { + msg = language == Language.Chinese ? "远离范围AOE死刑" : "AOE Tankbuster — Stay Away"; + } + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + boss4TankBusterList.Clear(); + } + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 4700, $"TankBusterKnockback-{ev.TargetId}", color: new Vector4(1, 0, 0, ColorAlpha)); + } + + + [ScriptMethod(name: "时神宿星落(月环) - Cronos Sling(Dynamo)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44366"])] + public void CronosSlingDynamo(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"月环靠近" : $"Dynamo (In) "; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 7200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 7200, $"CronosSlingDynamoSafe-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(70f), new Vector2(6f), 7200, $"CronosSlingDynamoSafe-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "时神宿星落(钢铁) - Cronos Sling(Chariot)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44365"])] + public void CronosSlingChariot(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"钢铁远离" : $"Chariot (Out) "; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 7200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(9f), 7200, $"CronosSlingDynamoSafe-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "时神宿星落(半场刀) - Cronos Sling(Haircut left/right)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44367|44368)$"])] + public void CronosSlingHaircut(Event ev, ScriptAccessory sa) + { + // 44367 right + // 44368 left + var rot = float.Pi / 2; + if (ev.ActionId == 44367) + { + rot = -float.Pi / 2; + } + + DrawHelper.DrawFanObject(sa, ev.SourceId, rot, new Vector2(70f), 180, 13000, $"CronosSlingHaircutRight-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + [ScriptMethod(name: "高天气旋 - Empyreal Vortex", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44400"])] + public void EmpyrealVortex(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? $"分散" : $"Spread"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(5f), 4700, $"EmpyrealVortex-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + + [ScriptMethod(name: "翘曲 - Warp", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44375"])] + public void Warp(Event ev, ScriptAccessory sa) + { + Vector3 Centerpos = new Vector3(800.00f, -900.00f, -800.00f); + var tarobj1 = IbcHelper.GetById(sa, ev.TargetId); + if (tarobj1 == null) return; + + DebugMsg($"distance: {Vector3.Distance(tarobj1.Position, Centerpos)})", sa); + if (Vector3.Distance(tarobj1.Position, Centerpos) < 15f) return; + + string msg = language == Language.Chinese ? $"前往传送点" : $"Move to the Portal"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawDisplacement(sa, tarobj1.Position, new Vector2(2, 4), 5500, $"Warp-{ev.TargetId}", color: sa.Data.DefaultSafeColor); + } + + + [ScriptMethod(name: "强催眠 - Sleepga", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44376"])] + public void Sleepga(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(70), 180, 2700, $"Sleepga-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "地神光尘流 - Gaea Stream", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44373"])] + public void GaeaStream(Event ev, ScriptAccessory sa) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"GaeaStream-{ev.SourceId}"; + dp.Color = new Vector4(1, 1, 0, ColorAlpha); + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(4, 24); + dp.DestoryAt = 1700; + dp.Rotation = float.Pi / 2; + dp.Offset = new Vector3(12, 0, 0); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + // 偷懒不画了 + [ScriptMethod(name: "欧米茄投枪 - Omega Javelin", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44382"], suppress: 5000)] + public void OmegaJavelin(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"远离标枪" : $"Away from Javelin"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + //0000002C|0000002D|0000002E|0000002F + private bool duplicatePhase2 = false; + [ScriptMethod(name: "复制 - Duplicate", eventType: EventTypeEnum.EnvControl, eventCondition: ["Index:regex:^(0000002[C-F]|000000(3[0-9])|0000003[A-D]|00000024)$"])] + public void Duplicate(Event ev, ScriptAccessory sa) + { + var index = ev.Index(); + var state = uint.Parse(ev["State"]?.ToString() ?? "0", System.Globalization.NumberStyles.HexNumber); + + DebugMsg($"EnvControl - Index: 0x{index:X} ({index}), State: 0x{state:X} ({state})", sa); + sa.Log.Debug($"EnvControl - Index: 0x{index:X} ({index}), State: 0x{state:X} ({state})"); + + DebugMsg($"con: {state == 0x00020001u}", sa); + sa.Log.Debug("$con: {state == 0x00020001u}"); + + switch (index) + { + case >= 0x2Cu and <= 0x34u when !duplicatePhase2 && state == 0x00020001u || duplicatePhase2 && state == 0x00080010u: + + DebugMsg($"1. Duplicate AOE at tile index: 0x{index:X}, duplicatePhase2: {duplicatePhase2}", sa); + sa.Log.Debug($"1. Duplicate AOE at tile index: 0x{index:X}, duplicatePhase2: {duplicatePhase2}"); + + var tile = index - 0x2Cu; + + AddDuplicateAOEs((int)(tile / 3), (int)(tile % 3), sa); + break; + + case >= 0x35u and <= 0x3Du: + DebugMsg($"2. Duplicate AOE at tile index: 0x{index:X}", sa); + sa.Log.Debug($"2. Duplicate AOE at tile index: 0x{index:X}"); + switch (state) + { + case 0x00080010u: + DebugMsg($"case 0x00080010u activated", sa); + sa.Log.Debug($"case 0x00080010u activated"); + var tile2 = index - 0x35u; + AddDuplicateAOEs((int)(tile2 / 3), (int)(tile2 % 3), sa); + break; + case 0x00020001u: + DebugMsg($"case 0x00020001u activated", sa); + sa.Log.Debug($"case 0x00020001u activated"); + var tile3 = index - 0x35u; + var tilePos = new Vector3(784f + tile3 % 3 * 16f, -900, -816f + tile3 / 3 * 16f) + new Vector3(0, 0, -8f); + DebugMsg($"Draw Duplicate at tile index: 0x{index:X}, pos: {tilePos}", sa); + sa.Log.Debug($"Draw Duplicate at tile index: 0x{index:X}, pos: {tilePos}"); + DrawHelper.DrawRectPosNoTarget(sa, tilePos, new Vector2(16f, 16f), 7800, $"Duplicate-{index}", new Vector4(1, 0, 0, ColorAlpha), drawMode: DrawModeEnum.Default); + + break; + } + break; + case 0x24u when state == 0x00200040u: + duplicatePhase2 = true; + DebugMsg("Duplicate Phase 2 activated", sa); + sa.Log.Debug("Duplicate Phase 2 activated"); + break; + } + } + + private void AddDuplicateAOEs(int row, int col, ScriptAccessory sa) + { + DebugMsg($"AddDuplicateAOEs at row: {row}, col: {col}", sa); + (int dr, int dc)[] offsets = + [ + (0, 0), + (-1, 0), + (1, 0), + (0, -1), + (0, 1), + ]; + + for (var i = 0; i < 5; ++i) + { + var nRow = row + offsets[i].dr; + var nCol = col + offsets[i].dc; + + sa.Log.Debug($"Checking offset {i}: nRow: {nRow}, nCol: {nCol}"); + if (nRow is >= 0 and < 3 && nCol is >= 0 and < 3) + { + var aoePos = new Vector3(784f + nCol * 16f, -900, -816f + nRow * 16f) + new Vector3(0, 0, -8f); + var duration = duplicatePhase2 ? 11100 : 10400; + + //DrawHelper.DrawRectPosNoTarget(sa, aoePos, , duration, $"DuplicateAOE-{nRow}-{nCol}", sa.Data.DefaultDangerColor); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"offset {i} - DuplicateAOE-{nRow}-{nCol}-pos: {aoePos}"; + dp.Color = new Vector4(1, 0, 0, ColorAlpha); + dp.Position = aoePos; + //dp.Rotation = -float.Pi / 2; + dp.Scale = new Vector2(16f, 16f); + dp.DestoryAt = duration; + dp.Offset = new Vector3(0, 0, 0); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + } + + [ScriptMethod(name: "复制清理 - Duplicate Clear", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44369|44370)$"], userControl: false)] + public void DuplicateClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw("Duplicate.*"); + sa.Method.RemoveDraw("DuplicateAOE.*"); + DebugMsg("Duplicate AOEs cleared", sa); + } + + [ScriptMethod(name: "星体破裂 - Stellar Burst", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44403)$"])] + public void StellarBurst(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"前往中间分摊" : $"Stack Mid"; + if (ev.TargetId == sa.Data.Me) + { + msg = language == Language.Chinese ? $"分摊点名" : $"Stack"; + } + + if (isText) sa.Method.TextInfo($"{msg}", duration: 6000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 12000, $"StellarBurst-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + + string Tornadoguid = ""; + private bool hasKnockback = false; + [ScriptMethod(name: "龙卷 - Tornado", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44395)$"])] + public async void Tornado(Event ev, ScriptAccessory sa) + { + Tornadoguid = sa.Method.RegistFrameworkUpdateAction(() => + { + //DebugMsg($"{IbcHelper.HasStatus(sa, sa.Data.MyObject, 1209)}", sa); + List knockBackStatus = new List { 1209, 160, 2663 }; + + for (int i = 0; i < knockBackStatus.Count; i++) + { + var hasStatus = IbcHelper.HasStatus(sa, sa.Data.MyObject, knockBackStatus[i]); + //DebugMsg($"Checking Status {knockBackStatus[i]}: {hasStatus}", sa); + if (hasStatus) + { + hasKnockback = true; + sa.Method.RemoveDraw($"Tornado-Danger:{ev.SourceId}"); + break; + } + } + + }); + + + if (!hasKnockback) + { + if (useAntiKnockback) sa.Method.UseAction(sa.Data.Me, 7559); + if (useAntiKnockback) sa.Method.UseAction(sa.Data.Me, 7548); + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(21f), 6000, $"Tornado-Danger:{ev.SourceId}", scaleByTime: false, color: new Vector4(1, 0, 0, ColorAlpha)); + } + + await Task.Delay(5700); + if (Tornadoguid != "") + { + sa.Method.UnregistFrameworkUpdateAction(Tornadoguid); + Tornadoguid = ""; + hasKnockback = false; + } + } + + + [ScriptMethod(name: "旋绕之火 & 旋绕之雷 - orbital flame & orbital levin", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(18747|18746)$"])] + public void orbitalFlameLevin(Event ev, ScriptAccessory sa) + { + // 18746 thunder + // 18747 fire + var scale = 3f; + var color = new Vector4(1, 1, 0, ColorAlpha); + var arrowScale = new Vector2(1, 3); + if (uint.Parse(ev["DataId"]?.ToString() ?? "0") == 18746) + { + scale = 1.5f; + color = new Vector4(0, 1, 1, ColorAlpha); + arrowScale = new Vector2(1, 2); + } + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"orbitalflame-{ev.SourceId}"; + dp.Color = color; + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(scale); + dp.DestoryAt = int.MaxValue; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + var dp1 = sa.Data.GetDefaultDrawProperties(); + dp1.Name = $"orbitalflame arraw-{ev.SourceId}"; + dp1.Color = sa.Data.DefaultSafeColor; + dp1.Owner = ev.SourceId; + dp1.Scale = arrowScale; + dp1.DestoryAt = int.MaxValue; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp1); + } + + [ScriptMethod(name: "旋绕之火 - orbital flame", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:regex:^(18747|18746)$"], userControl: false)] + public void orbitalflameClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"orbitalflame-{ev.SourceId}"); + sa.Method.RemoveDraw($"orbitalflame arraw-{ev.SourceId}"); + } + + + [ScriptMethod(name: "洪水 - Flood", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44390)$"])] + public void Flood(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(20f), 6000, $"Tornado-Danger:{ev.SourceId}", scaleByTime: false, color: new Vector4(1, 0, 0, ColorAlpha)); + } + + #endregion + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } +} + +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; + } + } +} +#region 计算函数 + +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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, MarkType.Attack1, true); + sa.Method.Mark(0xE000000, MarkType.Attack2, true); + sa.Method.Mark(0xE000000, MarkType.Attack3, true); + sa.Method.Mark(0xE000000, MarkType.Attack4, true); + sa.Method.Mark(0xE000000, MarkType.Attack5, true); + sa.Method.Mark(0xE000000, MarkType.Attack6, true); + sa.Method.Mark(0xE000000, MarkType.Attack7, true); + sa.Method.Mark(0xE000000, MarkType.Attack8, true); + sa.Method.Mark(0xE000000, MarkType.Bind1, true); + sa.Method.Mark(0xE000000, MarkType.Bind2, true); + sa.Method.Mark(0xE000000, MarkType.Bind3, true); + sa.Method.Mark(0xE000000, MarkType.Stop1, true); + sa.Method.Mark(0xE000000, MarkType.Stop2, true); + sa.Method.Mark(0xE000000, MarkType.Square, true); + sa.Method.Mark(0xE000000, MarkType.Circle, true); + sa.Method.Mark(0xE000000, MarkType.Cross, true); + sa.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 diff --git a/VeeverSW/07-DawnTrail/The-Jade-Stoa-Unreal.cs b/VeeverSW/07-DawnTrail/The-Jade-Stoa-Unreal.cs new file mode 100644 index 0000000..d90e094 --- /dev/null +++ b/VeeverSW/07-DawnTrail/The-Jade-Stoa-Unreal.cs @@ -0,0 +1,898 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using FFXIVClientStructs; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using FFXIVClientStructs; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace Veever.DawnTrail.The_Jade_Stoa_Unreal; + +[ScriptType(name: "LV.100 白虎幻巧战", territorys: [1239], guid: "29193d9d-a2c5-4a0d-875b-943a06790b95", + version: "0.0.0.5", author: "Veever", note: noteStr)] + +public class The_Jade_Stoa_Unreal +{ + const string noteStr = + """ + v0.0.0.5: + 1. 本脚本使用攻略为子言攻略,请在打本之前调整好!可达鸭的小队排序!(很重要,影响指路和机制播报) + 2. 如果懒得调也不想看需要小队位置判定的指路,可以在用户设置里面关闭指路开关 + 鸭门。 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + [UserSetting("指路开关")] + public bool isLead { get; set; } = true; + + [UserSetting("标点开关")] + public bool isMark { get; set; } = true; + + [UserSetting("本地标点开关(打开则为本地开关,关闭则为小队)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + public KodakkuAssist.Data.IGameObject? Boss { get; set; } + + public int HighestStakesCount; + public int OminousWindMarkerTTSCount; + public int HakuteiNotifyCount; + public int ShockStrikeCount; + + public bool isMTGroup; + + private readonly object OminousWindMarkerTTSLock = new object(); + private bool isDRTTS = false; + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + HighestStakesCount = 0; + OminousWindMarkerTTSCount = 0; + HakuteiNotifyCount = 0; + ShockStrikeCount = 0; + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] + public async void debug(Event @event, ScriptAccessory accessory) + { + //DebugMsg($"Me:{IbcHelper.GetMe().Name}", accessory); + //DebugMsg($"job:{IbcHelper.GetMe().ClassJob.Value.Name}", accessory); + } + + #region Phase 1 + [ScriptMethod(name: "风雷波动", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39933"])] + public void StormPulse(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 3000, true); + accessory.TTS("AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "天雷掌", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39931"])] + public void Tankbuster(Event @event, ScriptAccessory accessory) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + if (index == 0) + { + if (isText) accessory.Method.TextInfo("范围死刑, 注意减伤", duration: 2500, true); + accessory.TTS("范围死刑, 注意减伤", isTTS, isDRTTS); + } else + { + if (isText) accessory.Method.TextInfo("范围死刑, 远离MT", duration: 2500, true); + accessory.TTS("范围死刑, 远离MT", isTTS, isDRTTS); + } + } + + [ScriptMethod(name: "乾坤一掷位置记录", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:003E"])] + public void HighestStakesPosRecord(Event @event, ScriptAccessory accessory) + { + uint id = @event.TargetId(); + var boss = IbcHelper.GetById(accessory, id); + if (boss == null) + { + DebugMsg("未找到对应的对象", accessory); + return; + } + + Boss = boss; + } + + [ScriptMethod(name: "乾坤一掷", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39939"])] + public void HighestStakes(Event @event, ScriptAccessory accessory) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + + isMTGroup = (HighestStakesCount % 2 == 0) ? true : false; + if (Boss == null) + { + return; + } + + if (isMTGroup == true) + { + if (index == 1) + { + if (isText) accessory.Method.TextInfo("挑衅Boss", duration: 3700, true); + accessory.TTS("挑衅Boss", isTTS, isDRTTS); + } else + { + if (isText) accessory.Method.TextInfo("MT组前往分摊", duration: 3700, true); + accessory.TTS("MT组前往分摊", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "乾坤一掷"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = Boss.Position; + dp.Scale = new Vector2(6); + dp.DestoryAt = 5000; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + DebugMsg($"Me:{index}", accessory); + if (index == 2 || index == 4 || index == 5) + { + DebugMsg($"Me:MT组", accessory); + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"乾坤一掷指路MT组"; + dp1.Owner = accessory.Data.Me; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.ScaleMode |= ScaleMode.YByDistance; + dp1.TargetPosition = dp.Position; + dp1.Scale = new(2); + dp1.DestoryAt = 4500; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + } + } + + if (isMTGroup == false) + { + if (isText) accessory.Method.TextInfo("ST组前往分摊", duration: 3700, true); + accessory.TTS("ST组前往分摊", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "乾坤一掷"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Position = Boss.Position; + dp.Scale = new Vector2(6); + dp.DestoryAt = 5000; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + DebugMsg($"Me:{index}", accessory); + if (index == 3 || index == 6 || index == 7) + { + DebugMsg($"Me:ST组", accessory); + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"乾坤一掷指路ST组"; + dp1.Owner = accessory.Data.Me; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.ScaleMode |= ScaleMode.YByDistance; + dp1.TargetPosition = dp.Position; + dp1.Scale = new(2); + dp1.DestoryAt = 4500; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + + //if (Math.Abs(@event.SourceRotation()) > 1) + //{ + // DebugMsg($"rotation:{@event.SourceRotation()}", accessory); + // DebugMsg($"ABSrotation:{Math.Abs(@event.SourceRotation())}", accessory); + + //} else + //{ + // DebugMsg($"rotation:{@event.SourceRotation()}", accessory); + + // DebugMsg($"ABSrotation:{Math.Abs(@event.SourceRotation())}", accessory); + + + // } + } + HighestStakesCount++; + } + + [ScriptMethod(name: "荒弹标记", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:17801"])] + public void AratamaMarker(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "荒弹标记"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 0.784f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(1.5f); + dp.InnerScale = new Vector2(1.48f); + dp.DestoryAt = 10000; + dp.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp); + } + + [ScriptMethod(name: "空中东方躲球", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:39952"])] + public void EasternBall(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "空中东方躲球标记"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1f); + var pos = @event.EffectPosition(); + pos.Y = 0; + dp.Position = pos; + dp.Scale = new Vector2(2f); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "妖风标记", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:1481"])] + public void OminousWindMarker(Event @event, ScriptAccessory accessory) + { + lock(OminousWindMarkerTTSLock) + { + if (OminousWindMarkerTTSCount == 0) + { + if (isText) accessory.Method.TextInfo("被点名者互相远离", duration: 2500, true); + accessory.TTS("被点名者互相远离", isTTS, isDRTTS); + } + OminousWindMarkerTTSCount++; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "妖风标记"; + dp.Color = new Vector4(1.0f, 0.0f, 1.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(5f); + dp.InnerScale = new Vector2(4.95f); + dp.DestoryAt = 10000; + dp.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp); + } + } + + [ScriptMethod(name: "雷火一闪(白虎-半-39930)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39930"])] + public void FireandLightning_39930(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离Boss正面", duration: 2500, true); + accessory.TTS("远离Boss正面", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "雷火一闪39930"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(20f, 50f); + //dp.Radian = float.Pi; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "ST白帝拉怪提示", eventType: EventTypeEnum.Targetable, eventCondition: ["SourceName:regex:^(白帝|Hakutei)$"])] + public void HakuteiNotify(Event @event, ScriptAccessory accessory) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + if (HakuteiNotifyCount == 0) + { + if (index == 0) + { + if (isText) accessory.Method.TextInfo("MT将Boss带到场地北侧", duration: 3000, true); + accessory.TTS("MT将Boss带到场地北侧", isTTS, isDRTTS); + } + + if (index == 1) + { + if (isText) accessory.Method.TextInfo("白帝出现, ST注意拉仇前往场地南侧", duration: 3000, true); + accessory.TTS("白帝出现, ST注意拉仇前往场地南侧", isTTS, isDRTTS); + } + } + HakuteiNotifyCount++; + } + + [ScriptMethod(name: "荒弹", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0004"])] + public void Aratama(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + + if (index == 3 || index == 2) + { + if (isText) accessory.Method.TextInfo("奶妈前往场地东侧引导三次荒弹", duration: 2000, true); + accessory.TTS("奶妈前往场地东侧引导三次荒弹", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"荒弹奶妈指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = new Vector3(18, 0, 0); + dp.Scale = new(2); + dp.DestoryAt = 4500; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + if (index > 3) + { + if (isText) accessory.Method.TextInfo("DPS前往场地西侧引导三次荒弹", duration: 2000, true); + accessory.TTS("DPS前往场地西侧引导三次荒弹", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"荒弹DPS指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = new Vector3(-18, 0, 0); + dp.Scale = new(2); + dp.DestoryAt = 4500; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + } + + [ScriptMethod(name: "ST白帝冲提示", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0057"])] + public void WhiteHeraldNotify(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("ST前往场地南侧引导白帝冲", duration: 3700, true); + accessory.TTS("ST前往场地南侧引导白帝冲", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"ST白帝冲提示指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = new Vector3(2.13f, -0.00f, 19.39f); + dp.Scale = new(2); + dp.DestoryAt = 5000; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + + [ScriptMethod(name: "远雷提示(除ST)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39934"])] + public void DistantClap(Event @event, ScriptAccessory accessory) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + if (index != 1) + { + if (isText) accessory.Method.TextInfo("月环, 前往Boss脚下", duration: 2000, true); + accessory.TTS("月环, 前往Boss脚下", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "远雷提示"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(4f); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"远雷指路"; + dp1.Owner = accessory.Data.Me; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.ScaleMode |= ScaleMode.YByDistance; + dp1.TargetPosition = @event.SourcePosition(); + dp1.Scale = new(2); + dp1.DestoryAt = 5000; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + } + + [ScriptMethod(name: "雷火一闪(白帝-全-39935)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39935"])] + public void FireandLightning_39935(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离直线AOE", duration: 2000, true); + accessory.TTS("远离直线AOE", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "雷火一闪39935"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(20f, 50f); + //dp.Radian = float.Pi; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "雷轰", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39961"])] + public async void ShockStrike(Event @event, ScriptAccessory accessory) + { + if (ShockStrikeCount == 0) + { + if (isText) accessory.Method.TextInfo("集火白帝, 注意撞球", duration: 2500, true); + accessory.TTS("集火白帝, 注意撞球", isTTS, isDRTTS); + } else + { + if (isText) accessory.Method.TextInfo("集火白虎", duration: 2500, true); + accessory.TTS("集火白虎", isTTS, isDRTTS); + await Task.Delay(14700); + if (isText) accessory.Method.TextInfo("坦克LB", duration: 2500, true); + accessory.TTS("坦克LB", isTTS, isDRTTS); + } + + ShockStrikeCount++; + } + #endregion + #region Phase 2 + [ScriptMethod(name: "P2-1旋体脚", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39957"])] + public void SweeptheLeg(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("场中月环", duration: 2000, true); + accessory.TTS("场中月环", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "P2-1旋体脚标记"; + dp.Color = new Vector4(0.0f, 0.0f, 1.0f, 1.0f); + dp.Position = new Vector3(0, 0, 0); + dp.Scale = new Vector2(25f); + dp.InnerScale = new Vector2(5f); + dp.DestoryAt = 4800; + dp.ScaleMode = ScaleMode.ByTime; + dp.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp); + } + + [ScriptMethod(name: "p2荒弹", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:39953"])] + public void phase2Aratama(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "p2荒弹"; + dp.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(2f); + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "P2转场大AOE", eventType: EventTypeEnum.Chat, eventCondition: ["Message:regex:^(化为灰烬吧!| To ashes with you! | 塵芥と消えるがよい!)$"])] + public void phase2AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("大AOE", duration: 2500, true); + accessory.TTS("大AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "P2-2旋体脚", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39932"])] + public void P2SweeptheLeg(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("去Boss背后", duration: 3700, true); + accessory.TTS("去Boss背后", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "P2-2旋体脚"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(24f); + dp.Radian = float.Pi / 180 * 270; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "百雷缭乱", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:39942"])] + public void HundredfoldHavoc(Event @event, ScriptAccessory accessory) + { + var basePos = @event.EffectPosition(); + + List HundredfoldHavoclistNSEW = new List + { + new Vector3(-0.02f, -0.02f, -5.02f), //N[0] z-5 0 + new Vector3(-5.02f, -0.02f, -0.02f), //W[0] x-5 1 + new Vector3(4.99f, -0.02f, -0.02f), //E[0] x+5 2 + new Vector3(-0.02f, -0.02f, 4.99f), //S[0] z+5 3 + + new Vector3(-0.02f, 0f, -10.02f), //N[1] z-5 4 + new Vector3(-10.02f, 0f, -0.02f), //W[1] x-5 5 + new Vector3(9.99f, 0f, -0.02f), //E[1] x+5 6 + new Vector3(-0.02f, 0f, 9.99f), //S[1] z+5 7 + }; + + List HundredfoldHavoclistCorner = new List + { + new Vector3(-3.56f, -0.02f, -3.56f), //NW[0] z-5 0 + new Vector3(-3.56f, -0.02f, 3.52f), //SW[0] x-5 1 707 + new Vector3(3.52f, -0.02f, 3.52f), //SE[0] x+5 2 + new Vector3(3.52f, -0.02f, -3.56f), //NE[0] z+5 3 + + new Vector3(-7.12f, 0f, -7.12f), //N[1] z-5 4 + new Vector3(-7.12f, 0f, 7.04f), //W[1] x-5 5 + new Vector3(7.04f, 0f, 7.04f), //E[1] x+5 6 + new Vector3(7.04f, 0f, -7.12f), //S[1] z+5 7 + }; + + var dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = "百雷缭乱Base"; + dp0.Color = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); + dp0.Position = basePos; + dp0.Scale = new Vector2(5f); + dp0.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + + DebugMsg($"{@event.EffectPosition()}", accessory); + + if (basePos == HundredfoldHavoclistNSEW[0]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱Base"; + dp1.Color = new Vector4(1.0f, 1f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistNSEW[4]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + if (basePos == HundredfoldHavoclistNSEW[1]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱Base"; + dp1.Color = new Vector4(1.0f, 1f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistNSEW[5]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + if (basePos == HundredfoldHavoclistNSEW[2]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱Base"; + dp1.Color = new Vector4(1.0f, 1f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistNSEW[6]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + if (basePos == HundredfoldHavoclistNSEW[3]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱Base"; + dp1.Color = new Vector4(1.0f, 1f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistNSEW[7]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + //------ + + if (basePos == HundredfoldHavoclistCorner[0]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱"; + dp1.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistCorner[4]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + if (basePos == HundredfoldHavoclistCorner[1]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱"; + dp1.Color = new Vector4(1.0f, 1f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistCorner[5]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + if (basePos == HundredfoldHavoclistCorner[2]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱"; + dp1.Color = new Vector4(1.0f, 1f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistCorner[6]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + if (basePos == HundredfoldHavoclistCorner[3]) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "百雷缭乱"; + dp1.Color = new Vector4(1.0f, 1f, 0.0f, 1.0f); + dp1.Position = HundredfoldHavoclistCorner[7]; + dp1.Scale = new Vector2(5f); + dp1.InnerScale = new Vector2(4.95f); + dp1.Delay = 4700; + dp1.DestoryAt = 1000; + dp1.Radian = 2 * float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp1); + } + } + + [ScriptMethod(name: "炸弹低气压", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0065"])] + public void Bombogenesis(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + var index = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + + if (index == 0 || index == 1) + { + if (isText) accessory.Method.TextInfo("坦克去A点引导炸弹低气压", duration: 2500, true); + accessory.TTS("坦克去A点引导炸弹低气压", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"炸弹低气压坦克指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = new Vector3(-0.72f, -0.00f, -18.46f); + dp.Scale = new(2); + dp.DestoryAt = 4500; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + if (index == 2 || index == 3) + { + if (isText) accessory.Method.TextInfo("奶妈去B点引导炸弹低气压", duration: 2500, true); + accessory.TTS("奶妈去B点引导炸弹低气压", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"炸弹低气压奶妈指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = new Vector3(16.48f, -0.00f, 10.22f); + dp.Scale = new(2); + dp.DestoryAt = 4500; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + if (index > 3) + { + if (isText) accessory.Method.TextInfo("DPS去C点引导炸弹低气压", duration: 2500, true); + accessory.TTS("DPS去C点引导炸弹低气压", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"炸弹低气压Dps指路"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = new Vector3(-17.41f, 0.00f, 7.78f); + dp.Scale = new(2); + dp.DestoryAt = 4500; + if (isLead) accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + } + + [ScriptMethod(name: "炸弹低气压扩散", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2009431"])] + public void BombogenesisExpend(Event @event, ScriptAccessory accessory) + { + DebugMsg($"Operate: {@event.Operate()}", accessory); + if (@event.Operate() == "Add") + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "炸弹低气压扩散"; + dp.Color = new Vector4(1.0f, 0.0f, 1.0f, 1.0f); + dp.Position = @event.SourcePosition(); + dp.Scale = new Vector2(12f); + dp.DestoryAt = 20000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + DebugMsg($"{@event.SourcePosition()}", accessory); + } else + { + accessory.Method.RemoveDraw("炸弹低气压扩散"); + } + + } + + #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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + + +public static class Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + if (isTTS) + { + accessory.Method.TTS(text); + } + } +} + +public static class IbcHelper +{ + public static KodakkuAssist.Data.IGameObject? GetById(ScriptAccessory accessory, uint id) + { + return accessory.Data.Objects.SearchByEntityId(id); + } + + public static KodakkuAssist.Data.IGameObject? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me); + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } + + public static bool HasStatus(this IBattleChara ibc, uint statusId) + { + return ibc.StatusList.Any(x => x.StatusId == statusId); + } + + public static bool HasStatusAny(this IBattleChara ibc, uint[] statusIds) + { + return ibc.StatusList.Any(x => statusIds.Contains(x.StatusId)); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } +} diff --git a/VeeverSW/07-DawnTrail/TheWindwardWilds.cs b/VeeverSW/07-DawnTrail/TheWindwardWilds.cs new file mode 100644 index 0000000..5b735da --- /dev/null +++ b/VeeverSW/07-DawnTrail/TheWindwardWilds.cs @@ -0,0 +1,2428 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Common.Lua; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data.Parsing.Layer; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using System.Net.Http; +using static FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo.Delegates; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.DawnTrail.TheWindwardWilds; + +[ScriptType(name: Name, territorys: [1300], guid: "89c903db-488d-442d-9538-620460ef3966", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class TheWindwardWilds +{ + const string NoteStr = + $""" + v{Version} + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. 绘制结束后精简(删除)了一些TTS,如有哪个地方需要可以反馈给我添加回来 + 3. Boss模型和特效缩放比例设置请在用户设置中修改,1为默认值 + 鸭门 + ---------------------------------- + 1. If you need a draw or notice any issues, @ me on DC or DM me. + 2. Cleaned up a few TTS after the drawings. If you need any of them, let me know and I’ll restore them. + 3. Boss Model Scale and VFX Scale can be adjusted in User Settings, 1 is the Game default value. + Duckmen. + """; + + const string UpdateStr = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.100 护锁刃龙狩猎战 [The Windward Wilds]"; + private const string Version = "0.0.0.7"; + private const string DebugVersion = "a"; + private const string UpdateInfo = UpdateStr; + + private const bool Debugging = false; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static readonly Vector3 Center = new Vector3(100, 0, 100); + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("Boss模型缩放比例(Boss Model Scale)")] + public static float BossModelScale { get; set; } = 1f; + + [UserSetting("Boss特效缩放比例(Boss VFX Scale)")] + public static float BossVFXScale { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useaction { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + + private enum GuardianArkveldPhase + { + Phase1, + Phase2, + } + + private static GuardianArkveldPhase guardianArkveldPhase = GuardianArkveldPhase.Phase1; + public uint BossDataId = 18658; + public int ChainbladeBlowTripleCount = 0; + + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + guardianArkveldPhase = GuardianArkveldPhase.Phase1; + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + ChainbladeBlowTripleCount = 0; + + SpecialFunction.SetModelScale(sa, BossDataId, BossModelScale, BossVFXScale); + + _ = ScriptVersionChecker.CheckVersionAsync( + sa, + "89c903db-488d-442d-9538-620460ef3966", + Version, + showNotification: true + ); + } + + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43886|45201)$"], + userControl: true)] + public void AOENotify(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + ChainbladeBlowTripleCount = 0; + } + + #region Phase 1 + [ScriptMethod(name: "P1锁刃敲打 - P1 Chainblade Blow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43833|43832)$"])] + public void P1ChainbladeBlow(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 43833 boss right + // 43832 boss left + case 43833: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 6899, $"Chainblade Blow right-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(7f, 0, 20f)); + break; + } + case 43832: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 6899, $"Chainblade Blow left-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(-7f, 0, 20f)); + break; + } + } + } + + [ScriptMethod(name: "P1锁刃飞翔突进【龙闪】 - P1 Wyvern's Siegeflight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43838|43839)$"])] + public void P1WyvernsSiegeflight(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 43838 small + // 43839 split + case 43838: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 6200, $"Wyvern's Siegeflight mid-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha)); + break; + } + case 43839: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3400, $"Wyvern's Siegeflight Left-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(12f, 0, 0f), delay: 6600); + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3400, $"Wyvern's Siegeflight Right-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(-12f, 0, 0f), delay: 6600); + break; + } + } + } + + [ScriptMethod(name: "P1锁刃飞翔突进【护龙共振】 - P1 Guardian Siegeflight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43835|43836)$"])] + public void P1GuardianSiegeflight(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 43835 small + // 43836 large + case 43835: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 6200, $"Guardian Siegeflight mid-{ev.SourceId}", new Vector4(0, 1, 1, ColorAlpha)); + break; + } + case 43836: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(16f, 40f), 3500, $"Guardian Siegeflight mid-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), delay: 6500); + break; + } + } + } + + + [ScriptMethod(name: "直线递进【龙闪】 - Line AoEs [Wyvern's Siegeflight]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43877)$"])] + public void LineAoEs(Event ev, ScriptAccessory sa) + { + var spos = ev.SourcePosition; + var srot = ev.SourceRotation; + + float[] distances = { 0f, 8f, 16f, 24f }; + int[] durations = { 2200, 2700, 2700, 2700 }; + int[] delays = { 0, 2200, 5200, 7600 }; + + for (int i = 0; i < distances.Length; i++) + { + float worldX = spos.X + distances[i] * MathF.Sin(srot); + float worldZ = spos.Z + distances[i] * MathF.Cos(srot); + Vector3 aoePosition = new Vector3(worldX, spos.Y, worldZ); + + DrawHelper.DrawRectPosNoTarget(sa, aoePosition, new Vector2(40f, 8f), srot, durations[i], + $"Line AoEs [Wyvern's Siegeflight]-{ev.SourceId}-{i}", + new Vector4(1, 0, 0, ColorAlpha), + delay: delays[i]); + } + } + + [ScriptMethod(name: "锁刃敲打三连 - Chainblade Blow 3-hit", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43844|43845|43846|43847)$"])] + public async void ChainbladeBlowTriple(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + case 43844: + { + if (ChainbladeBlowTripleCount == 0) + { + string msg = language == Language.Chinese ? "前往第三个钢铁边缘,随后进入躲避" : "To Third Chariot edge, then in"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + if (ChainbladeBlowTripleCount != 2) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 7200, $"Chainblade Blow Circle-{ev.SourceId}", sa.Data.DefaultDangerColor); + } else + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 7200, $"Chainblade Blow Circle-{ev.SourceId}", new Vector4(1, 1, 0, ColorAlpha)); + + await Task.Delay(10000); + ChainbladeBlowTripleCount = 0; + } + + ChainbladeBlowTripleCount++; + break; + } + case 43845: + { + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(14f), new Vector2(8f), 2000, + $"Chainblade Blow Dount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 7200); + break; + } + case 43846: + { + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(20f), new Vector2(14f), 2000, + $"Chainblade Blow Dount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 9200); + break; + } + case 43847: + { + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(26f), new Vector2(20f), 2000, + $"Chainblade Blow Dount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 11200); + break; + } + } + } + + + [ScriptMethod(name: "分摊 - Stack", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0064)$"])] + public void Stack(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分摊点名" : "Stack"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(4.5f), 6000, "Stack", color: sa.Data.DefaultSafeColor); + } + + + [ScriptMethod(name: "P1回旋锁刃【龙闪】 - P1 Wyvern's Ouroblade", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43849|43851)$"])] + public void P1WyvernsOuroblade(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 43849 left + // 43851 right + case 43849: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, float.Pi / 2, new Vector2(40f), 180, 6700, $"Wyvern's Ouroblade left-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + case 43851: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, -float.Pi / 2, new Vector2(40f), 180, 6700, $"Wyvern's Ouroblade right-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + } + } + + [ScriptMethod(name: "护龙共振 - Guardian Resonance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43853)$"], suppress: 5000)] + public void GuardianResonance(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"集合 → 放圈 → 踩塔" : $"Stack → drop circles → soak towers"; + string msg1 = language == Language.Chinese ? $"集合放圈后踩塔" : $"Stack and drop circles then soak towers"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5000, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + } + + [ScriptMethod(name: "护龙共振坦克提示 - Guardian Resonance Tank Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43854)$"])] + public void GuardianResonanceTank(Event ev, ScriptAccessory sa) + { + var myobj = sa.Data.MyObject; + if (myobj == null) return; + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Tank") + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(4f), 13000, $"Guardian Resonance Tank-{ev.SourceId}", + sa.Data.DefaultSafeColor, scaleByTime: false); + } + else + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(4f), 13000, $"Guardian Resonance Tank-{ev.SourceId}", + new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + } + + [ScriptMethod(name: "波状龙闪 - Wyvern's Vengeance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43860)$"])] + public void WyvernsVengeance(Event ev, ScriptAccessory sa) + { + //DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 4700, $"Wyvern's Vengeance-{ev.SourceId}", + // sa.Data.DefaultDangerColor, scaleByTime: false); + //DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 4700, $"Wyvern's Vengeance-{ev.SourceId}", + // sa.Data.DefaultDangerColor, scaleByTime: false, offset: new Vector3(8, 0, 0), delay: 4700); + //DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 4700, $"Wyvern's Vengeance-{ev.SourceId}", + // sa.Data.DefaultDangerColor, scaleByTime: false, offset: new Vector3(16, 0, 0), delay: 6500); + var effectPos = ev.EffectPosition; + var srot = ev.SourceRotation; + + float[] distances = { 0f, 8f, 16f }; + int[] delays = { 0, 4700, 6500 }; + int[] durations = { 4700, 1800, 1800 }; + + for (int i = 0; i < distances.Length; i++) + { + float worldX = effectPos.X + distances[i] * MathF.Sin(srot); + float worldZ = effectPos.Z + distances[i] * MathF.Cos(srot); + Vector3 circlePos = new Vector3(worldX, effectPos.Y, worldZ); + + DrawHelper.DrawCircle(sa, circlePos, new Vector2(6f), durations[i], + $"Wyvern's Vengeance-{ev.SourceId}-{i}", + new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false, delay: delays[i]); + } + } + + [ScriptMethod(name: "大龙闪(大圈地火) - Wyvern's Radiance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44808)$"])] + public void WyvernsRadiance(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(12f), 1700, $"Wyvern's Radiance-{ev.SourceId}", + new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + #endregion + + [ScriptMethod(name: "转场AOE提示 - Phase Change AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43870)$"], + userControl: true)] + public void PCAOENotify(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "三连AOE" : "Triple AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + guardianArkveldPhase = GuardianArkveldPhase.Phase2; + } + + #region Phase 2 + [ScriptMethod(name: "龙闪炮 - Wyvern's Weal", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45048|45049)$"])] + public void WyvernsWeal(Event ev, ScriptAccessory sa) + { + var spos = ev.SourcePosition; + var srot = ev.SourceRotation; + + switch (ev.ActionId) + { + case 45048: + DrawHelper.DrawFanObject(sa, ev.SourceId, MathTools.DegToRad(-60), new Vector2(50f), 110, 7700, $"Wyvern's Weal clockwise-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + case 45049: + DrawHelper.DrawFanObject(sa, ev.SourceId, MathTools.DegToRad(60), new Vector2(50f), 110, 7700, $"Wyvern's Weal clockwise-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + + float forwardDist = 9f; // 向前 + float Dist = 4.5f; // 向右 + float worldX; + float worldZ; + worldX = spos.X + forwardDist * MathF.Sin(srot) - Dist * MathF.Cos(srot); + worldZ = spos.Z + forwardDist * MathF.Cos(srot) + Dist * MathF.Sin(srot); + + // 45049 anti-clockwise + Vector3 antiClockwisePos = new Vector3(worldX, spos.Y, worldZ); + // 45048 clockwise + worldX = spos.X + forwardDist * MathF.Sin(srot) + Dist * MathF.Cos(srot); + worldZ = spos.Z + forwardDist * MathF.Cos(srot) - Dist * MathF.Sin(srot); + Vector3 clockwisePos = new Vector3(worldX, spos.Y, worldZ); + + if (isLead) + { + string msg = language == Language.Chinese ? "前往安全区域" : "Move to Safe Place"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } else + { + if (ev.ActionId == 45049) + { + string msg = language == Language.Chinese ? "前往左上角躲避" : "Move to TL corner"; + string msg1 = language == Language.Chinese ? "前往左上角躲避" : "Move to Top Left corner"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + } else + { + string msg = language == Language.Chinese ? "前往右上角躲避" : "Move to TR corner"; + string msg1 = language == Language.Chinese ? "前往左上角躲避" : "Move to Top right corner"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + } + + } + + switch (ev.ActionId) + { + case 45049: + if (isLead) DrawHelper.DrawDisplacement(sa, antiClockwisePos, new Vector2(2f), 6000, "Wyvern's Weal Displacement"); + break; + case 45048: + if (isLead) DrawHelper.DrawDisplacement(sa, clockwisePos, new Vector2(2f), 6000, "Wyvern's Weal Displacement"); + break; + } + } + + [ScriptMethod(name: "P2锁刃飞翔突进【护龙共振】 - P2 Guardian Siegeflight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45069|45070)$"], + userControl: true)] + public void P2GuardianSiegeflight(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 45069 small + // 45070 large + case 45069: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 6200, $"Guardian Siegeflight mid-{ev.SourceId}", new Vector4(0, 1, 1, ColorAlpha)); + break; + } + case 45070: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(16f, 40f), 3500, $"Guardian Siegeflight mid-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), delay: 6500); + break; + } + } + } + + [ScriptMethod(name: "P2锁刃敲打 - P2 Chainblade Blow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45059|45056)$"])] + public void P2ChainbladeBlow(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 45059 boss right + // 45056 boss left + case 45059: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 6899, $"Chainblade Blow right-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(7f, 0, 20f)); + break; + } + case 45056: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 6899, $"Chainblade Blow left-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(-7f, 0, 20f)); + break; + } + } + } + + [ScriptMethod(name: "P2锁刃飞翔突进【龙闪】 - P2 Wyvern's Siegeflight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45071|45072)$"])] + public void P2WyvernsSiegeflight(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 45071 small + // 45072 split + case 45071: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 6200, $"Wyvern's Siegeflight mid-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha)); + break; + } + case 45072: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3400, $"Wyvern's Siegeflight Left-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(12f, 0, 0f), delay: 6600); + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3400, $"Wyvern's Siegeflight Right-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(-12f, 0, 0f), delay: 6600); + break; + } + } + } + + [ScriptMethod(name: "P2回旋锁刃【龙闪】 - P2 Wyvern's Ouroblade", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45061|45063)$"])] + public void P2WyvernsOuroblade(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 45061 left + // 45063 right + case 45061: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, float.Pi / 2, new Vector2(40f), 180, 5700, $"Wyvern's Ouroblade left-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + case 45063: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, -float.Pi / 2, new Vector2(40f), 180, 5700, $"Wyvern's Ouroblade right-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + } + } + + [ScriptMethod(name: "龙尾突刺 - Steeltail Thrust", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44804)$"])] + public void SteeltailThrust(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTargetWithRot(sa, ev.SourceId, new Vector2(6f, 60f), float.Pi, 3300, $"Steeltail Thrust-{ev.SourceId}", sa.Data.DefaultDangerColor); + string msg = language == Language.Chinese ? "远离背后" : "Avoid behind the boss"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + public Vector3 WrathfulRattlePos = new Vector3(0, 0, 0); + public float WrathfulRattleRot = 0f; + [ScriptMethod(name: "锁哭龙闪·改 - Wrathful Rattle", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43880)$"])] + public void WrathfulRattle(Event ev, ScriptAccessory sa) + { + var spos = ev.SourcePosition; + WrathfulRattlePos = spos; + var srot = ev.SourceRotation; + WrathfulRattleRot = srot; + + int[] delays = { 3200, 6200, 9200, 12200 }; + int[] durations = { 3000, 3000, 3000, 3000 }; + + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 3200, $"Wrathful Rattle main-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 20)); + + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 6f + i * 4f; + + float rightX = WrathfulRattlePos.X - 20 * MathF.Sin(WrathfulRattleRot) + sideDist * MathF.Cos(WrathfulRattleRot); + float rightZ = WrathfulRattlePos.Z - 20 * MathF.Cos(WrathfulRattleRot) - sideDist * MathF.Sin(WrathfulRattleRot); + Vector3 rightPos = new Vector3(rightX, WrathfulRattlePos.Y, rightZ); + + + float leftX = WrathfulRattlePos.X - 20 * MathF.Sin(WrathfulRattleRot) - sideDist * MathF.Cos(WrathfulRattleRot); + float leftZ = WrathfulRattlePos.Z - 20 * MathF.Cos(WrathfulRattleRot) + sideDist * MathF.Sin(WrathfulRattleRot); + Vector3 leftPos = new Vector3(leftX, WrathfulRattlePos.Y, leftZ); + + DrawHelper.DrawRectPosNoTarget(sa, rightPos, new Vector2(4f, 40f), WrathfulRattleRot, durations[i], + $"Wrathful Rattle right-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + + DrawHelper.DrawRectPosNoTarget(sa, leftPos, new Vector2(4f, 40f), WrathfulRattleRot, durations[i], + $"Wrathful Rattle left-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + } + } + + [ScriptMethod(name: "锁哭龙闪·改(折返) - Wrathful Rattle Return", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43882)$"])] + public void WrathfulRattleReturn(Event ev, ScriptAccessory sa) + { + var Epos = new Vector3(118.00f, 0.00f, 100.00f); + var Wpos = new Vector3(82.00f, 0.00f, 100.00f); + var spos = ev.SourcePosition; + var srot = ev.SourceRotation; + + int[] delays = { 2800, 4500, 7000, 9500, 12000, 15000, 17500, 20000, 22500 }; + int[] durations = { 1700, 1700, 2500, 2500, 3000, 2500, 2500, 2500, 2500 }; + + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 2800, $"Wrathful Rattle main-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 20)); + + if (Vector3.Distance(spos, Epos) < 10) + { + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 4f + i * 4f; + + float X = spos.X + sideDist * MathF.Cos(srot); + float Z = spos.Z - 20 * MathF.Cos(srot) + sideDist * MathF.Sin(srot); + Vector3 Pos = new Vector3(X, spos.Y, Z); + + DrawHelper.DrawRectPosNoTarget(sa, Pos, new Vector2(4f, 40f), srot, durations[i], + $"Wrathful Rattle right-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + DebugMsg($"Pos: {Pos}, srot: {srot}, MathF.Cos(srot) {MathF.Cos(srot)}", sa); + } + } + else if (Vector3.Distance(spos, Wpos) < 10) + { + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 4f + i * 4f; + + float X = spos.X - sideDist * MathF.Cos(srot); + float Z = spos.Z - 20 * MathF.Cos(srot) + sideDist * MathF.Sin(srot); + Vector3 Pos = new Vector3(X, spos.Y, Z); + + DrawHelper.DrawRectPosNoTarget(sa, Pos, new Vector2(4f, 40f), srot, durations[i], + $"Wrathful Rattle left-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + } + } + } + + [ScriptMethod(name: "Unit - Remove & Refresh ALL", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:73"], + userControl: Debugging)] + public void Uninit(Event ev, ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug($"Uninit Triggered"); + sa.Method.RemoveDraw(".*"); + + sa.Method.ClearFrameworkUpdateAction(this); + } + + #endregion + + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战) / "Ranged DPS"(远程) / "Unknown" / "None" + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + return playerObject.ClassJob.Value.Role switch + { + 1 => "Tank", // 坦克 + 4 => "Healer", // 治疗 + 2 => "Melee DPS", // 近战DPS + 3 => "Ranged DPS", // 远程DPS + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 + + +#region 脚本版本检查 +public static class ScriptVersionChecker +{ + private const string OnlineRepoUrl = "https://raw.githubusercontent.com/VeeverSW/Kodakku-Script/refs/heads/main/OnlineRepo.json"; + private static readonly HttpClient _httpClient = new HttpClient(); + + /// + /// 在线仓库脚本信息 + /// + public class OnlineScriptInfo + { + public string Name { get; set; } = ""; + public string Guid { get; set; } = ""; + public string Version { get; set; } = ""; + public string Author { get; set; } = ""; + public string Repo { get; set; } = ""; + public string DownloadUrl { get; set; } = ""; + public string Note { get; set; } = ""; + public string UpdateInfo { get; set; } = ""; + public int[] TerritoryIds { get; set; } = Array.Empty(); + } + + /// + /// 版本 + /// + public enum VersionCompareResult + { + /// 当前版本较新或相同 + UpToDate, + /// 有新版本可用 + UpdateAvailable, + /// 未找到匹配的脚本 + NotFound, + /// 检查失败 + Error + } + + /// + /// 检查脚本版本 + /// + /// ScriptAccessory + /// 脚本GUID + /// 当前版本号 + /// 是否显示通知 + /// 版本比较结果 + public static async Task<(VersionCompareResult result, OnlineScriptInfo? onlineInfo)> CheckVersionAsync( + ScriptAccessory sa, + string guid, + string currentVersion, + bool showNotification = true) + { + try + { + sa.Log.Debug($"开始检查脚本版本 (GUID: {guid}, 当前版本: {currentVersion})"); + + var response = await _httpClient.GetStringAsync(OnlineRepoUrl); + var onlineScripts = JsonConvert.DeserializeObject>(response); + + if (onlineScripts == null || onlineScripts.Count == 0) + { + sa.Log.Error("无法解析在线仓库数据"); + return (VersionCompareResult.Error, null); + } + + var onlineScript = onlineScripts.FirstOrDefault(s => + s.Guid.Equals(guid, StringComparison.OrdinalIgnoreCase)); + + if (onlineScript == null) + { + //sa.Log.Debug($"在线仓库中未找到 GUID 为 {guid} 的脚本"); + //if (showNotification) + //{ + // sa.Method.TextInfo("该脚本未在在线仓库中注册", 3000); + //} + return (VersionCompareResult.NotFound, null); + } + + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 找到在线脚本: {onlineScript.Name}, 在线版本: {onlineScript.Version}"); + + var compareResult = CompareVersions(currentVersion, onlineScript.Version); + + if (compareResult < 0) + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新 (当前: {currentVersion})"); + if (showNotification) + { + sa.Method.TextInfo( + $"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新\n当前版本: {currentVersion}", + 5000, + true); + } + return (VersionCompareResult.UpdateAvailable, onlineScript); + } + else + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 当前版本已是最新 (当前: {currentVersion}, 在线: {onlineScript.Version})"); + + return (VersionCompareResult.UpToDate, onlineScript); + } + } + catch (HttpRequestException ex) + { + //sa.Log.Error($"网络请求失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败: 网络错误", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + catch (Exception ex) + { + //sa.Log.Error($"版本检查失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + } + + /// + /// 比较版本号 + /// + /// 版本1 (例如: "0.0.0.3") + /// 版本2 (例如: "0.0.0.5") + /// 负数: version1 < version2, 0: 相等, 正数: version1 > version2 + private static int CompareVersions(string version1, string version2) + { + var v1Parts = version1.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + var v2Parts = version2.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + + int maxLength = Math.Max(v1Parts.Length, v2Parts.Length); + + for (int i = 0; i < maxLength; i++) + { + int v1Part = i < v1Parts.Length ? v1Parts[i] : 0; + int v2Part = i < v2Parts.Length ? v2Parts[i] : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } +} +#endregion diff --git a/VeeverSW/07-DawnTrail/TheWindwardWildsExtreme.cs b/VeeverSW/07-DawnTrail/TheWindwardWildsExtreme.cs new file mode 100644 index 0000000..843529e --- /dev/null +++ b/VeeverSW/07-DawnTrail/TheWindwardWildsExtreme.cs @@ -0,0 +1,3614 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Common.Lua; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Xml.Linq; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.DawnTrail.TheWindwardWildsExtreme; + +[ScriptType(name: Name, territorys: [1306], guid: "b4a3871d-2499-4152-aaa2-a911ee1bbce2", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateStr)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class TheWindwardWildsExtreme +{ + const string NoteStr = + $""" + v{Version} + ----- 感谢@Usami提供的惰性水晶绘制方法 ----- + ----- 请在使用前阅读注意事项 以及根据情况修改用户设置 ----- + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. 播报左右侧均是以 #面对# Boss为准, 播报的并不是基于boss本体的左右,请注意辨别 + 3. 本脚本使用攻略为game8(子言) + 4. 如果想要场景标记请确保你打开了ACT并且安装了鲶鱼精邮差(PostNamazu)插件 + 5. 大部分机制处理都依赖于小队成员顺序,请务必确保小队成员顺序正确 + 6. 绘制结束后精简(删除)了一些TTS,如有哪个地方需要可以反馈给我添加回来 + 7. 分摊机制如果奶妈倒了的话可能会出现一些妙妙bug + 8. 网卡可能会小概率出现一些妙妙bug + 9. 龙闪炮处会有推荐指路和分摊指路,推荐指路为绿色,分摊指路为紫色,请注意区分 + 10. Boss模型和特效缩放比例设置请在用户设置中修改,1为默认值 + 11. 可以在用户设置中的职能设置来手动设置自己的职能,默认defalutRole为使用可达鸭职能顺序 + 鸭门 + ---------------------------------- + ----- Thanks to @Usami for the distance-based Cracked Crystal drawing method.----- + ----- Please read the notes before use and adjust user settings as needed. ----- + 1. If you need a draw or notice any issues, @ me on DC or DM me. + 2. Left/Right notifies are **boss-relative** (based on you facing the boss), + not the boss model’s own left/right. Please keep this in mind. + 3. This script follows the Game8 & 子言 guide. + 4. If you want WayMarkers, make sure ACT is running and the PostNamazu plugin is installed. + 5. Most drawing depends on **party list order** — please make sure the party order is correct. + 6. Cleaned up a few TTS after the drawings. If you need any of them, let me know and I’ll restore them. + 7. Stack drawings may behave oddly if a healer goes down. + 8. There may be some odd bugs with network lag. + 9. Wyvern’s Weal now has both recommend and stack guide arrow. + The recommended guide arrow is shown in green, and the stack guide arrow is shown in purple. + 10. Boss Model Scale and VFX Scale can be adjusted in User Settings, 1 is the Game default value. + 11. You can now manually set your Role in the User Settings → Role Settings. + `默认defalutRole` follows KodakkuAssist’s Role order. + Duckmen. + """; + + const string UpdateStr = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.100 护锁刃龙上位狩猎战 [The Windward Wilds (Extreme)]"; + private const string Version = "0.0.1.0"; + private const string DebugVersion = "a"; + + private const bool Debugging = false; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static readonly Vector3 Center = new Vector3(100, 0, 100); + + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("Boss模型缩放比例(Boss Model Scale)")] + public static float BossModelScale { get; set; } = 1f; + + [UserSetting("Boss特效缩放比例(Boss VFX Scale)")] + public static float BossVFXScale { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useaction { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + [UserSetting("职能设置(Role Settings)")] + public IndexMode RoleSetting { get; set; } = IndexMode.默认defaultRole; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + [UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + public bool PostNamazuPrint { get; set; } = true; + + [UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + public int PostNamazuPort { get; set; } = 2019; + + [UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + private static bool _initHint = false; + + public uint GuardianArkveldDataId = 18661; + public int ChainbladeBlowTripleCount = 0; + public bool StackInProgress = false; + public bool IsWestEastTower = true; + + private readonly object CountLock = new object(); + private readonly object _crystalsLock = new object(); + private readonly object GroupStackLock = new object(); + + List StackPlayerId = new List(); + Dictionary ClamorousChaseDict = new Dictionary(); + List ChainbladeBlowTriplePos = new List(); + + private enum GuardianArkveldPhase + { + Phase1, + Phase2, + } + + private static GuardianArkveldPhase guardianArkveldPhase = GuardianArkveldPhase.Phase1; + + + public void DebugMsg(string str, ScriptAccessory sa) + { + if (!isDebug) return; + sa.Log.Debug($"[DEBUG] {str}"); + } + + private ScriptAccessory _sa = null; + + // Class Defined + public class Crystal(IGameObject obj, bool drawn) + { + public IGameObject Obj { get; set; } = obj; + public bool Drawn { get; set; } = drawn; + } + + // Params + private List _crystals = []; + + // Trigger + private bool _crossExaflareCrystalTriggered = false; // 十字地火 + private bool _dragonBeamCrystalTriggered = false; // 龙闪炮 + private bool _edgeExaflareCrystalTriggered = false; // 场边地火 + + // Framework Guid + private string _crystalCloseGuid = ""; + + // TTS Cooldown + private long _lastStackTTSTime = 0; + private const long TTS_COOLDOWN = 3000; + + // My Index + //private int myIndex = -1; + private static readonly List role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + public enum IndexMode + { + + 默认defaultRole = -1, + MT = 0, + ST = 1, + H1 = 2, + H2 = 3, + D1 = 4, + D2 = 5, + D3 = 6, + D4 = 7 + } + + + public void Init(ScriptAccessory sa) + { + guardianArkveldPhase = GuardianArkveldPhase.Phase1; + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + if (PostNamazuPrint) PostWaymark(sa); + + RefreshParams(); + _sa = sa; + + sa.Method.ClearFrameworkUpdateAction(this); + + _ = ScriptVersionChecker.CheckVersionAsync( + sa, + "b4a3871d-2499-4152-aaa2-a911ee1bbce2", + Version, + showNotification: true + ); + + SpecialFunction.SetModelScale(sa, GuardianArkveldDataId, BossModelScale, BossVFXScale); + } + + private void RefreshParams() + { + _crystals = []; + + _crossExaflareCrystalTriggered = false; + _dragonBeamCrystalTriggered = false; + _edgeExaflareCrystalTriggered = false; + _initHint = false; + IsWestEastTower = true; + StackInProgress = false; + + _crystalCloseGuid = ""; + ChainbladeBlowTripleCount = 0; + + ClamorousChaseDict.Clear(); + ChainbladeBlowTriplePos.Clear(); + } + + #region Waymark + private static readonly Vector3 posA = new Vector3(100.00f, -0.00f, 82.7f); + private static readonly Vector3 posB = new Vector3(118.00f, -0.00f, 100.00f); + private static readonly Vector3 posC = new Vector3(100.00f, -0.00f, 118.00f); + private static readonly Vector3 posD = new Vector3(81.5f, -0.00f, 100.00f); + private static readonly Vector3 posOne = Center; + + + public void PostWaymark(ScriptAccessory accessory) + { + var waymark = new NamazuHelper.Waymark(accessory); + waymark.AddWaymarkType("A", posA); + waymark.AddWaymarkType("B", posB); + waymark.AddWaymarkType("C", posC); + waymark.AddWaymarkType("D", posD); + waymark.AddWaymarkType("One", posOne); + + waymark.SetJsonPayload(PostNamazuisLocal, PostNamazuisLocal); + waymark.PostWaymarkCommand(PostNamazuPort); + } + #endregion + + [ScriptMethod(name: "主动进行场地标点 - Place waymarks", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdk"], + userControl: Debugging)] + public void userNamazuPost(Event ev, ScriptAccessory sa) + { + PostWaymark(sa); + } + + [ScriptMethod(name: "Change Scale", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdk"], + userControl: Debugging)] + public void setModelscale(Event ev, ScriptAccessory sa) + { + SpecialFunction.SetModelScale(sa, GuardianArkveldDataId, BossModelScale, BossVFXScale); + } + + [ScriptMethod(name: "---- 时间轴记录 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void SplitLine_Timeline(Event ev, ScriptAccessory sa) + { + + // DataId + + + // ActionId + + + // StatusId + + + // TetherId + + //太多了占地方不放了 + + } + + //[ScriptMethod(name: "set JobIndex", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:regex:^(?i)(mt|st|h1|h2|d1|d2|d3|d4)$"], userControl: Debugging)] + //public void SetMyIndex(Event ev, ScriptAccessory sa) + //{ + // var input = ev["Message"]?.ToString()?.ToUpper(); + // if (string.IsNullOrEmpty(input)) return; + + // var index = role.FindIndex(r => r.Equals(input, StringComparison.OrdinalIgnoreCase)); + // if (index >= 0) + // { + // myIndex = index; + // if (isDebug) sa.Log.Debug($"MyIndex manually set to: {myIndex} ({role[myIndex]})"); + + // string msg = language == Language.Chinese ? $"/e 职能已设置为 {role[myIndex]}" : $"/e Role has been set -> {role[myIndex]}"; + // sa.Method.SendChat($"{msg}"); + // } + //} + + [ScriptMethod(name: "JobIndexTest", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:regex:^(?i)(index)$"], userControl: Debugging)] + public void MyIndex(Event ev, ScriptAccessory sa) + { + if (isDebug) sa.Method.SendChat($"/e MyIndex is: {GetMyIndex(sa)} ({role[GetMyIndex(sa)]}), index: {sa.Data.PartyList.IndexOf(sa.Data.Me)}"); + } + + public int GetMyIndex(ScriptAccessory sa) + { + var myIndex = -1; + if (RoleSetting != IndexMode.默认defaultRole) + { + myIndex = (int)RoleSetting; + } + else + { + myIndex = sa.Data.PartyList.IndexOf(sa.Data.Me); + } + if (isDebug) sa.Log.Debug($"MyIndex set to: {myIndex} ({role[myIndex]})"); + return myIndex; + } + + [ScriptMethod(name: "身份提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43950)$"], + userControl: true)] + public void RoleTips(Event ev, ScriptAccessory sa) + { + ChainbladeBlowTripleCount = 0; + + if (_initHint) return; + _initHint = true; + + List role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + + string msg = language == Language.Chinese ? + $"/e 你是【{role[GetMyIndex(sa)]}】," + + $"若有误请及时调整。" : + $"/e You are【{role[GetMyIndex(sa)]}】," + + $"Please adjust if incorrect."; + + sa.Method.SendChat($"{msg}"); + } + + private IGameObject? GetBossObject(ScriptAccessory sa) + { + return sa.GetByDataId(GuardianArkveldDataId).FirstOrDefault(); + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43950|43951|45202)$"], + userControl: true)] + public void AOENotify(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + ChainbladeBlowTripleCount = 0; + ClamorousChaseDict.Clear(); + ChainbladeBlowTriplePos.Clear(); + ChainbladeBlowTripleCount = 0; + } + + #region Phase 1 + [ScriptMethod(name: "P1锁刃敲打 - P1 Chainblade Blow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43892|43891)$"])] + public void P1ChainbladeBlow(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 43892 boss right first + // 43891 boss left first + case 43892: + { + string msg = language == Language.Chinese ? "左侧 ←← 右侧" : "Left ←← Right"; + string msg1 = language == Language.Chinese ? "先右侧再左侧" : "Right then Left"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 6700, $"Chainblade Blow rightP1-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(7f, 0, 20f)); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 3500, $"Chainblade Blow leftP1-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(-7f, 0, 20f), delay: 6700); + break; + } + case 43891: + { + string msg = language == Language.Chinese ? "左侧 →→ 右侧" : "Left →→ Right"; + string msg1 = language == Language.Chinese ? "先左侧再右侧" : "Left then Right"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 6700, $"Chainblade Blow leftP1-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(-7f, 0, 20f)); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(28f, 80f), 3500, $"Chainblade Blow rightP1-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(7f, 0, 20f), delay: 6700); + break; + } + } + } + + [ScriptMethod(name: "锁刃飞翔突进【龙闪】 - Wyvern's Siegeflight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45111|43905|45104)$"])] + public void WyvernsSiegeflight(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 45111 small + // 43905 split + // + case 45111 or 45104: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 6200, $"Wyvern's Siegeflight mid-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha)); + break; + } + case 43905: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3000, $"Wyvern's Siegeflight Left-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(12f, 0, 0f), delay: 6000); + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3000, $"Wyvern's Siegeflight Right-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(-12f, 0, 0f), delay: 6000); + break; + } + } + } + + [ScriptMethod(name: "P1锁刃飞翔突进【护龙共振】 - P1 Guardian Siegeflight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43900|43906|45099)$"], suppress: 1000)] + public void P1GuardianSiegeflight(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 43835 small + // 43836 large + case 43900 or 45099: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 6200, $"Guardian Siegeflight mid-{ev.SourceId}", new Vector4(0, 1, 1, ColorAlpha)); + break; + } + case 43906: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(16f, 40f), 3000, $"Guardian Siegeflight mid-{ev.SourceId}", + new Vector4(1, 0, 0, ColorAlpha), delay: 6200); + break; + } + } + } + + + [ScriptMethod(name: "直线递进【龙闪】 - Line AoEs [Wyvern's Siegeflight]", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43940)$"])] + public void LineAoEs(Event ev, ScriptAccessory sa) + { + var spos = ev.SourcePosition; + var srot = ev.SourceRotation; + + float[] distances = { 0f, 8f, 16f, 24f }; + int[] durations = { 2200, 2700, 2700, 2700 }; + int[] delays = { 0, 2200, 5200, 7600 }; + + for (int i = 0; i < distances.Length; i++) + { + float worldX = spos.X + distances[i] * MathF.Sin(srot); + float worldZ = spos.Z + distances[i] * MathF.Cos(srot); + Vector3 aoePosition = new Vector3(worldX, spos.Y, worldZ); + + DrawHelper.DrawRectPosNoTarget(sa, aoePosition, new Vector2(40f, 8f), srot, durations[i], + $"Line AoEs [Wyvern's Siegeflight]-{ev.SourceId}-{i}", + new Vector4(1, 0, 0, ColorAlpha), + delay: delays[i]); + } + } + + + [ScriptMethod(name: "锁刃敲打三连 - Chainblade Blow 3-hit", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43911|43912|43913|43914)$"])] + public void ChainbladeBlowTriple(Event ev, ScriptAccessory sa) + { + lock (CountLock) + { + switch (ev.ActionId) + { + case 43911: + { + ChainbladeBlowTriplePos.Add(ev.EffectPosition); + if (ChainbladeBlowTripleCount == 0) + { + string msg = language == Language.Chinese ? "前往第三个钢铁边缘,随后进入躲避" : "To Third Chariot edge, then in"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + if (ChainbladeBlowTripleCount == 1) + { + var bossObj = GetBossObject(sa); + if (bossObj == null) return; + DrawHelper.DrawRectPosTarget(sa, bossObj.Position, ChainbladeBlowTriplePos[0], new Vector2(12f, 33f), 5700, + "Connect", color: new Vector4(1, 0, 1, ColorAlpha)); + + DrawHelper.DrawRectPosTarget(sa, ChainbladeBlowTriplePos[0], ChainbladeBlowTriplePos[1], new Vector2(12f, 24f), 6500, + "Connect", color: new Vector4(1, 0, 1, ColorAlpha)); + } + + if (ChainbladeBlowTripleCount == 2) + { + DrawHelper.DrawRectPosTarget(sa, ChainbladeBlowTriplePos[1], ChainbladeBlowTriplePos[2], new Vector2(12f, 24f), 7500, + "Connect", color: new Vector4(1, 0, 1, ColorAlpha)); + } + + if (ChainbladeBlowTripleCount != 2) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 7200, $"Chainblade Blow Circle-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + else + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 7200, $"Chainblade Blow Circle-{ev.SourceId}", new Vector4(1, 1, 0, ColorAlpha)); + } + + ChainbladeBlowTripleCount++; + break; + } + case 43912: + { + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(14f), new Vector2(8f), 2000, + $"Chainblade Blow Dount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 7200); + break; + } + case 43913: + { + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(20f), new Vector2(14f), 2000, + $"Chainblade Blow Dount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 9200); + break; + } + case 43914: + { + DrawHelper.DrawDount(sa, ev.EffectPosition, new Vector2(26f), new Vector2(20f), 2000, + $"Chainblade Blow Dount-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false, delay: 11200); + break; + } + } + } + } + + [ScriptMethod(name: "分组分摊 - Group Stack", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0064)$"])] + public async void GroupStack(Event ev, ScriptAccessory sa) + { + lock (CountLock) + { + DebugMsg("In Group Stack Method Check", sa); + StackPlayerId.Add(ev.TargetId); + if (StackPlayerId.Count < 2) return; + } + lock (GroupStackLock) + { + StackInProgress = true; + DebugMsg("In Group Stack Method", sa); + var targetindex0 = sa.Data.PartyList.IndexOf((uint)StackPlayerId[0]); + var targetindex1 = sa.Data.PartyList.IndexOf((uint)StackPlayerId[1]); + + var object0 = IbcHelper.GetById(sa, StackPlayerId[0]); + var object1 = IbcHelper.GetById(sa, StackPlayerId[1]); + if (object0 == null || object1 == null) return; + + // Check H1 Group + bool IsH1Group(int index) => index is 0 or 2 or 4 or 5; + + if (isDebug) DebugMsg($"targetindex0: {targetindex0}, targetindex1: {targetindex1}, myIndex: {GetMyIndex(sa)}", sa); + + // myobj got targeted + if (StackPlayerId.Contains(sa.Data.Me)) + { + bool nearBoss = IsH1Group(GetMyIndex(sa)); + string msg = language == Language.Chinese + ? (nearBoss ? "靠近Boss分摊" : "远离Boss分摊") + : (nearBoss ? "Stack Near Boss" : "Stack Away Boss"); + + if (isDebug) DebugMsg(nearBoss ? "A" : "B", sa); + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + DrawHelper.DrawCircleObject(sa, sa.Data.Me, new Vector2(4.5f), 7700, + $"Stack {(nearBoss ? "Near" : "Away")}", color: sa.Data.DefaultSafeColor); + } + else + { + // myobj no targeted + int targetIndex = -1; + ulong targetPlayerId = 0; + IGameObject? targetObject = null; + + if (IsH1Group(GetMyIndex(sa))) + { + // H1 Group + if (IsH1Group(targetindex0)) + { + targetIndex = 0; + targetPlayerId = StackPlayerId[0]; + targetObject = object0; + } + else if (IsH1Group(targetindex1)) + { + targetIndex = 1; + targetPlayerId = StackPlayerId[1]; + targetObject = object1; + } + } + else + { + // H2 Group + if (!IsH1Group(targetindex0)) + { + targetIndex = 0; + targetPlayerId = StackPlayerId[0]; + targetObject = object0; + } + else if (!IsH1Group(targetindex1)) + { + targetIndex = 1; + targetPlayerId = StackPlayerId[1]; + targetObject = object1; + } + } + + // Find Same Group Target + if (targetIndex >= 0 && targetObject != null) + { + if (isDebug) DebugMsg($"Going to stack with index {targetIndex}", sa); + + string msg = language == Language.Chinese + ? $"与{IbcHelper.GetObjectName(targetObject)}分摊" + : $"Stack with {IbcHelper.GetObjectName(targetObject)}"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + + long currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (isTTS && (currentTime - _lastStackTTSTime > TTS_COOLDOWN)) + { + sa.Method.TTS($"{msg}"); + _lastStackTTSTime = currentTime; + } + + DrawHelper.DrawCircleObject(sa, targetPlayerId, new Vector2(4.5f), 7700, + $"Stack with {IbcHelper.GetObjectName(targetObject)}", color: sa.Data.DefaultSafeColor); + if (isLead) DrawHelper.DrawDisplacementObject(sa, targetPlayerId, new Vector2(2f), 7700, + $"Stack Displacement with {IbcHelper.GetObjectName(targetObject)}", color: new Vector4(1, 0, 1, 2)); + } + } + } + await Task.Delay(7000); + StackPlayerId.Clear(); + StackInProgress = false; + } + + [ScriptMethod(name: "分摊 - Stack", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0064)$"])] + public async void Stack(Event ev, ScriptAccessory sa) + { + await Task.Delay(1100); + if (StackInProgress) return; + + DebugMsg("In Stack Method", sa); + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分摊点名" : "Stack"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(4.5f), 6000, "Stack", color: sa.Data.DefaultSafeColor); + } + + [ScriptMethod(name: "P1回旋锁刃【龙闪】 - P1 Wyvern's Ouroblade", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43916|43918)$"])] + public void P1WyvernsOuroblade(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 43916 left + // 43918 right + case 43916: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, float.Pi / 2, new Vector2(40f), 180, 6700, $"Wyvern's Ouroblade left-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + case 43918: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, -float.Pi / 2, new Vector2(40f), 180, 6700, $"Wyvern's Ouroblade right-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + } + } + + // 踩塔 + List WestEastTowersPos = new List + { + new Vector3(88.00f, 0.00f, 96.00f), // Main Tank West + new Vector3(110.00f, 0.00f, 102.00f), // Side Tank East + + new Vector3(92.00f, 0.00f, 106.00f), // D1 SW + new Vector3(102.00f, 0.00f, 110.00f), // D2 SE + + new Vector3(96.00f, 0.00f, 88.00f), // D3 NW + new Vector3(108.00f, 0.00f, 90.00f), // D4 NE + }; + + List NorthSouthTowersPos = new List + { + new Vector3(102.00f, 0.00f, 90.00f), // Main Tank North + new Vector3(96.00f, 0.00f, 112.00f), // Side Tank South + + new Vector3(88.00f, 0.00f, 104.00f), // D1 SW1 + new Vector3(108.00f, 0.00f, 108.00f), // D2 SE1 + + new Vector3(90.00f, 0.00f, 92.00f), // D3 NW + new Vector3(110.00f, 0.00f, 98.00f), // D4 NE1 + + }; + + [ScriptMethod(name: "Check Tower", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43921)$"], userControl: false)] + public void CheckTower(Event ev, ScriptAccessory sa) + { + lock (CountLock) + { + if (Vector3.Distance(ev.SourcePosition, WestEastTowersPos[0]) < 3f || + Vector3.Distance(ev.SourcePosition, WestEastTowersPos[1]) < 3f) + { + IsWestEastTower = true; + } + else + { + IsWestEastTower = false; + } + } + } + + [ScriptMethod(name: "护龙共振 - Guardian Resonance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43919)$"])] + public async void GuardianResonance(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? $"1点集合 → 放3次圈 → 踩塔 (黄圈不要引导到塔上)" : $"Stack at point 1 → drop 3 circles → soak towers (No Puddles on Towers)"; + string msg1 = language == Language.Chinese ? $"衣点集合放圈后踩塔" : $"Stack at point one and drop circles then soak towers"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5000, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + + List role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + await Task.Delay(1000); + if (IsWestEastTower) + { + switch (GetMyIndex(sa)) + { + case 0: // MT + DrawHelper.DrawCircle(sa, WestEastTowersPos[0], new Vector2(4f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, WestEastTowersPos[0], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 1: // ST + DrawHelper.DrawCircle(sa, WestEastTowersPos[1], new Vector2(4f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, WestEastTowersPos[1], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 4: // D1 + DrawHelper.DrawCircle(sa, WestEastTowersPos[2], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, WestEastTowersPos[2], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 5: // D2 + DrawHelper.DrawCircle(sa, WestEastTowersPos[3], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, WestEastTowersPos[3], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 6: // D3 + DrawHelper.DrawCircle(sa, WestEastTowersPos[4], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, WestEastTowersPos[4], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 7: // D4 + DrawHelper.DrawCircle(sa, WestEastTowersPos[5], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, WestEastTowersPos[5], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + default: + await Task.Delay(4500); + string msg2 = language == Language.Chinese ? $"治疗玩家补塔" : $"Healer Cover Tower"; + if (isText) sa.Method.TextInfo($"{msg2}", duration: 5000, true); + if (isTTS) sa.Method.TTS($"{msg2}"); + break; + } + } else + { + switch (GetMyIndex(sa)) + { + case 0: // MT + DrawHelper.DrawCircle(sa, NorthSouthTowersPos[0], new Vector2(4f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, NorthSouthTowersPos[0], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 1: // ST + DrawHelper.DrawCircle(sa, NorthSouthTowersPos[1], new Vector2(4f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, NorthSouthTowersPos[1], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 4: // D1 + DrawHelper.DrawCircle(sa, NorthSouthTowersPos[2], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, NorthSouthTowersPos[2], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 5: // D2 + DrawHelper.DrawCircle(sa, NorthSouthTowersPos[3], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, NorthSouthTowersPos[3], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 6: // D3 + DrawHelper.DrawCircle(sa, NorthSouthTowersPos[4], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, NorthSouthTowersPos[4], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + case 7: // D4 + DrawHelper.DrawCircle(sa, NorthSouthTowersPos[5], new Vector2(2f), 11000, $"Guardian Resonance-{Role[GetMyIndex(sa)]}", + sa.Data.DefaultSafeColor, scaleByTime: false, drawmode: DrawModeEnum.Imgui); + if (isLead) DrawHelper.DrawDisplacement(sa, NorthSouthTowersPos[5], new Vector2(2f), 11000, $"Guardian Resonance Navi-{Role[GetMyIndex(sa)]}"); + break; + default: + await Task.Delay(4500); + string msg2 = language == Language.Chinese ? $"治疗玩家补塔" : $"Healer Cover Tower"; + if (isText) sa.Method.TextInfo($"{msg2}", duration: 5000, true); + if (isTTS) sa.Method.TTS($"{msg2}"); + break; + } + } + } + + + [ScriptMethod(name: "波状龙闪 - Wyvern's Vengeance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43926)$"])] + public void WyvernsVengeance(Event ev, ScriptAccessory sa) + { + var effectPos = ev.EffectPosition; + var srot = ev.SourceRotation; + + float[] distances = { 0f, 8f, 16f }; + int[] delays = { 0, 0, 0 }; + int[] durations = { 4700, 6500, 8300 }; + + for (int i = 0; i < distances.Length; i++) + { + float worldX = effectPos.X + distances[i] * MathF.Sin(srot); + float worldZ = effectPos.Z + distances[i] * MathF.Cos(srot); + Vector3 circlePos = new Vector3(worldX, effectPos.Y, worldZ); + + DrawHelper.DrawCircle(sa, circlePos, new Vector2(6f), durations[i], + $"Wyvern's Vengeance-{ev.SourceId}-{i}", + new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false, delay: delays[i]); + } + } + + [ScriptMethod(name: "波状龙闪惰性水晶绘制 - Wyvern's Vengeance Cracked Crystal Drawing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43926)$"], + userControl: true, suppress: 2000)] + public void CrossExaflareCrystalFw(Event ev, ScriptAccessory sa) + { + // Framework Start + if (_crossExaflareCrystalTriggered) return; + + _crossExaflareCrystalTriggered = true; + sa.Log.Debug($"水晶实时距离计算,范围显示开启"); + _crystalCloseGuid = sa.Method.RegistFrameworkUpdateAction(CrystalCloseFrameworkAction); + } + + [ScriptMethod(name: "Wyvern's Vengeance - Init Other Framworks", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43926)$"], + userControl: Debugging, suppress: 2000)] + public void CrossExaflareCrystalFwRefresh(Event ev, ScriptAccessory sa) + { + // _crossExaflareCrystalTriggered = false; + _dragonBeamCrystalTriggered = false; + _edgeExaflareCrystalTriggered = false; + } + + [ScriptMethod(name: "Crystals Recorder", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(1866[23])$"], + userControl: Debugging)] + public void CrystalRecord(Event ev, ScriptAccessory sa) + { + // 18662 small + // 18663 large + lock (_crystals) + { + var obj = sa.GetById(ev.SourceId); + if (obj is null) return; + + _crystals.Add(new Crystal(obj, false)); + + var dataId = JsonConvert.DeserializeObject(ev["DataId"]); + if (isDebug) sa.Log.Debug($"记录下第 {_crystals.Count} 个 {(dataId == 18662 ? "小" : "大")} 水晶"); + } + } + [ScriptMethod(name: "Remove Crystals", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4392[45])$", "TargetIndex:1"], + userControl: Debugging)] + public void CrystalSplashRemove(Event ev, ScriptAccessory sa) + { + lock (_crystalsLock) + { + var obj = sa.GetById(ev.SourceId); + if (obj is null) return; + + sa.WriteVisible(obj, false); + + sa.Method.RemoveDraw($"水晶-{ev.SourceId}"); + _crystals.RemoveAll(x => x.Obj.GameObjectId == ev.SourceId); + + if (isDebug) sa.Log.Debug($"水晶{ev.SourceId}爆炸,删除,List内剩余{_crystals.Count}"); + } + } + + [ScriptMethod(name: "大龙闪(大圈地火) - Wyvern's Radiance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44808)$"])] + public void WyvernsRadiance(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(12f), 1700, $"Wyvern's Radiance-{ev.SourceId}", + new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + #endregion + + [ScriptMethod(name: "转场AOE提示 - Phase Change AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43935)$"], + userControl: true)] + public void PCAOENotify(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "三连AOE" : "Triple AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + guardianArkveldPhase = GuardianArkveldPhase.Phase2; + ClamorousChaseDict.Clear(); + ChainbladeBlowTriplePos.Clear(); + ChainbladeBlowTripleCount = 0; + } + + #region Phase 2 + [ScriptMethod(name: "Clamorous Chase Check", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0194|0195|0196|0197|0198|0199|019A|019B)$"], + userControl: false)] + public void ClamorousChaseCheck(Event ev, ScriptAccessory sa) + { + // TargetIcon + // 0194 ONE 1 + // 0195 TWO 2 + // 0196 THREE 3 + // 0197 FOUR 4 + // 0198 FIVE 5 + // 0199 SIX 6 + // 019A SEVEN 7 + // 019B EIGHT 8 + lock (CountLock) + { + var index = 0; + DebugMsg($"ClamorousChaseCheck Detected, id: {ev["Id"]}", sa); + + switch (ev["Id"]) + { + case "0194": + index = 1; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 1 added", sa); + break; + case "0195": + index = 2; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 2 added", sa); + break; + case "0196": + index = 3; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 3 added", sa); + break; + case "0197": + index = 4; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 4 added", sa); + break; + case "0198": + index = 5; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 5 added", sa); + break; + case "0199": + index = 6; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 6 added", sa); + break; + case "019A": + index = 7; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 7 added", sa); + break; + case "019B": + index = 8; + ClamorousChaseDict.Add(index, ev.TargetId); + DebugMsg($"ClamorousChaseDict 8 added", sa); + break; + } + } + } + + [ScriptMethod(name: "锁刃跃动 - Clamorous Chase", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43955|43958)$"])] + public void ClamorousChase(Event ev, ScriptAccessory sa) + { + // 43955 clockwise + // 43958 anti-clockwise + lock (CountLock) + { + Vector3 posASafe = new Vector3(100.00f, 0f, 92f); + if (ev.ActionId == 43955) + { + DebugMsg("Clamorous Chase 43955 clockwise Detected", sa); + if (ClamorousChaseDict.ContainsKey(1) && ClamorousChaseDict[1] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 1 Detected", sa); + DrawHelper.DrawCircle(sa, posB, new Vector2(2f), 8900, + $"Clamorous Chase 1-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posB, new Vector2(2f), 8900, + $"Clamorous Chase Navi 1-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 1-{ev.SourceId}", delay: 8900); + } + + if (ClamorousChaseDict.ContainsKey(2) && ClamorousChaseDict[2] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 2 Detected", sa); + DrawHelper.DrawCircle(sa, posC, new Vector2(2f), 12000, + $"Clamorous Chase 2-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posC, new Vector2(2f), 12000, + $"Clamorous Chase Navi 2-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 2-{ev.SourceId}", delay: 12000); + } + + if (ClamorousChaseDict.ContainsKey(3) && ClamorousChaseDict[3] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 3 Detected", sa); + DrawHelper.DrawCircle(sa, posD, new Vector2(2f), 15100, + $"Clamorous Chase 3-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posD, new Vector2(2f), 15100, + $"Clamorous Chase Navi 3-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 3-{ev.SourceId}", delay: 15100); + } + + if (ClamorousChaseDict.ContainsKey(4) && ClamorousChaseDict[4] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 4 Detected", sa); + DrawHelper.DrawCircle(sa, posD, new Vector2(2f), 8900, + $"Clamorous Chase 4-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posASafe, new Vector2(2f), 8900, + $"Clamorous Chase Navi 4-{ev.SourceId}"); + + string msg = language == Language.Chinese ? "先在安全区等待Boss攻击后再引导" : "Wait in the safe zone, then bait."; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posA, new Vector2(2f), 9300, + $"Clamorous Chase Navi 4-{ev.SourceId}", delay: 8900); + + if (isLead) DrawHelper.DrawCircle(sa, posA, new Vector2(2f), 9300, + $"Clamorous Chase 4-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 8900); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 4-{ev.SourceId}", delay: 18200); + } + + if (ClamorousChaseDict.ContainsKey(5) && ClamorousChaseDict[5] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 5 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 13300, + $"Clamorous Chase 5-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 13300, + $"Clamorous Chase Navi 5-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posB, new Vector2(2f), 8000, + $"Clamorous Chase Navi 5-{ev.SourceId}", delay: 13300); + + if (isLead) DrawHelper.DrawCircle(sa, posB, new Vector2(2f), 8000, + $"Clamorous Chase 5-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 13300); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 5-{ev.SourceId}", delay: 21300); + } + + if (ClamorousChaseDict.ContainsKey(6) && ClamorousChaseDict[6] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 6 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 16400, + $"Clamorous Chase 6-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 16400, + $"Clamorous Chase Navi 6-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posC, new Vector2(2f), 8000, + $"Clamorous Chase Navi 6-{ev.SourceId}", delay: 16400); + + DrawHelper.DrawCircle(sa, posC, new Vector2(2f), 8000, + $"Clamorous Chase 6-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 16400); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 6-{ev.SourceId}", delay: 24400); + } + + + if (ClamorousChaseDict.ContainsKey(7) && ClamorousChaseDict[7] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 7 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 19500, + $"Clamorous Chase 7-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 19500, + $"Clamorous Chase Navi 7-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posD, new Vector2(2f), 8000, + $"Clamorous Chase Navi 7-{ev.SourceId}", delay: 19500); + + DrawHelper.DrawCircle(sa, posD, new Vector2(2f), 8000, + $"Clamorous Chase 7-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 19500); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 7-{ev.SourceId}", delay: 27500); + } + + if (ClamorousChaseDict.ContainsKey(8) && ClamorousChaseDict[8] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 8 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 22600, + $"Clamorous Chase 8-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 22600, + $"Clamorous Chase Navi 8-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posA, new Vector2(2f), 8000, + $"Clamorous Chase Navi 8-{ev.SourceId}", delay: 22600); + + DrawHelper.DrawCircle(sa, posA, new Vector2(2f), 8000, + $"Clamorous Chase 8-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 22600); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 8-{ev.SourceId}", delay: 30600); + } + } + else + { + DebugMsg("Clamorous Chase 43958 anti-clockwise Detected", sa); + if (ClamorousChaseDict.ContainsKey(1) && ClamorousChaseDict[1] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 1 Detected", sa); + DrawHelper.DrawCircle(sa, posD, new Vector2(2f), 8900, + $"Clamorous Chase 1-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posD, new Vector2(2f), 8900, + $"Clamorous Chase Navi 1-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 1-{ev.SourceId}", delay: 8900); + } + + if (ClamorousChaseDict.ContainsKey(2) && ClamorousChaseDict[2] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 2 Detected", sa); + DrawHelper.DrawCircle(sa, posC, new Vector2(2f), 12000, + $"Clamorous Chase 2-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posC, new Vector2(2f), 12000, + $"Clamorous Chase Navi 2-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 2-{ev.SourceId}", delay: 12000); + } + + if (ClamorousChaseDict.ContainsKey(3) && ClamorousChaseDict[3] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 3 Detected", sa); + DrawHelper.DrawCircle(sa, posB, new Vector2(2f), 15100, + $"Clamorous Chase 3-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posB, new Vector2(2f), 15100, + $"Clamorous Chase Navi 3-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 3-{ev.SourceId}", delay: 15100); + } + + if (ClamorousChaseDict.ContainsKey(4) && ClamorousChaseDict[4] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 4 Detected", sa); + DrawHelper.DrawCircle(sa, posASafe, new Vector2(2f), 8900, + $"Clamorous Chase 4-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posASafe, new Vector2(2f), 8900, + $"Clamorous Chase Navi 4-{ev.SourceId}"); + + string msg = language == Language.Chinese ? "先在安全区等待Boss攻击后再引导" : "Wait in the safe zone, then bait."; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + + if (isLead) DrawHelper.DrawDisplacement(sa, posA, new Vector2(2f), 9300, + $"Clamorous Chase Navi 4-{ev.SourceId}", delay: 8900); + + DrawHelper.DrawCircle(sa, posA, new Vector2(2f), 9300, + $"Clamorous Chase 4-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 8900); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 4-{ev.SourceId}", delay: 18200); + } + + if (ClamorousChaseDict.ContainsKey(5) && ClamorousChaseDict[5] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 5 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 13300, + $"Clamorous Chase 5-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 13300, + $"Clamorous Chase Navi 5-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posD, new Vector2(2f), 8000, + $"Clamorous Chase Navi 5-{ev.SourceId}", delay: 13300); + + DrawHelper.DrawCircle(sa, posD, new Vector2(2f), 8000, + $"Clamorous Chase 5-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 13300); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 5-{ev.SourceId}", delay: 21300); + } + + if (ClamorousChaseDict.ContainsKey(6) && ClamorousChaseDict[6] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 6 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 16400, + $"Clamorous Chase 6-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 16400, + $"Clamorous Chase Navi 6-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posC, new Vector2(2f), 8000, + $"Clamorous Chase Navi 6-{ev.SourceId}", delay: 16400); + + DrawHelper.DrawCircle(sa, posC, new Vector2(2f), 8000, + $"Clamorous Chase 6-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 16400); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 6-{ev.SourceId}", delay: 24400); + } + + + if (ClamorousChaseDict.ContainsKey(7) && ClamorousChaseDict[7] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 7 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 19500, + $"Clamorous Chase 7-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 19500, + $"Clamorous Chase Navi 7-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posB, new Vector2(2f), 8000, + $"Clamorous Chase Navi 7-{ev.SourceId}", delay: 19500); + + DrawHelper.DrawCircle(sa, posB, new Vector2(2f), 8000, + $"Clamorous Chase 7-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 19500); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 7-{ev.SourceId}", delay: 27500); + } + + if (ClamorousChaseDict.ContainsKey(8) && ClamorousChaseDict[8] == sa.Data.Me) + { + DebugMsg("Clamorous Chase 8 Detected", sa); + DrawHelper.DrawCircle(sa, posOne, new Vector2(2f), 22600, + $"Clamorous Chase 8-{ev.SourceId}", + sa.Data.DefaultSafeColor); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 22600, + $"Clamorous Chase Navi 8-{ev.SourceId}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, posA, new Vector2(2f), 8000, + $"Clamorous Chase Navi 8-{ev.SourceId}", delay: 22600); + + DrawHelper.DrawCircle(sa, posA, new Vector2(2f), 8000, + $"Clamorous Chase 8-{ev.SourceId}", + sa.Data.DefaultSafeColor, delay: 22600); + + if (isLead) DrawHelper.DrawDisplacement(sa, posOne, new Vector2(2f), 4000, + $"Clamorous Chase Navi 8-{ev.SourceId}", delay: 30600); + } + } + } + } + + [ScriptMethod(name: "龙闪炮标记 - Wyvern's Weal Target", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(01D6)$"], suppress: 30000)] + public void WyvernsWealTarget(Event ev, ScriptAccessory sa) + { + var RightTopPos = new Vector3(110.74f, 0f, 84.63f); + var LeftTopPos = new Vector3(89.87f, 0f, 83.45f); + var RightMidPos = new Vector3(118.30f, 0.00f, 96.75f); + var LeftMidPos = new Vector3(82.35f, 0.00f, 96.75f); + + if (ev.TargetId == sa.Data.Me) + { + if (isLead) DrawHelper.DrawDisplacement(sa, RightTopPos, new Vector2(2f), 7700, "Wyvern's Weal Target Lead Navi"); + string msg = language == Language.Chinese ? "右上角落引导" : "Bait at top-right corner"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + if (isLead) DrawHelper.DrawDisplacement(sa, LeftTopPos, new Vector2(2f), 9000, "Wyvern's Weal Target Lead Navi", delay: 7700); + // Check H1 Group + bool IsH1Group(int index) => index is 0 or 2 or 4 or 5; + + if (IsH1Group(GetMyIndex(sa))) + { + if (isLead) DrawHelper.DrawDisplacement(sa, RightTopPos, new Vector2(2f), 9000, "Wyvern's Weal Target Lead Navi", delay: 16700); + } else + { + if (isLead) DrawHelper.DrawDisplacement(sa, RightMidPos, new Vector2(2f), 9000, "Wyvern's Weal Target Lead Navi", delay: 16700); + } + } else + { + if (isLead) DrawHelper.DrawDisplacement(sa, LeftTopPos, new Vector2(2f), 7700, "Wyvern's Weal Target Lead Navi"); + string msg = language == Language.Chinese ? "左上角落等待" : "Wait at top-left corner"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + bool IsH1Group(int index) => index is 0 or 2 or 4 or 5; + + if (IsH1Group(GetMyIndex(sa))) + { + if (isLead) DrawHelper.DrawDisplacement(sa, RightTopPos, new Vector2(2f), 9000, "Wyvern's Weal Target Lead Navi", delay: 16700); + } + else + { + if (isLead) DrawHelper.DrawDisplacement(sa, RightMidPos, new Vector2(2f), 9000, "Wyvern's Weal Target Lead Navi", delay: 16700); + } + + if (isLead) DrawHelper.DrawDisplacement(sa, LeftTopPos, new Vector2(2f), 9000, "Wyvern's Weal Target Lead Navi", delay: 25700); + } + } + + [ScriptMethod(name: "龙闪炮惰性水晶绘制 - Wyvern's Weal Cracked Crystal Drawing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43937)$"], + userControl: true, suppress: 2000)] + public void DragonBeamCrystalFw(Event ev, ScriptAccessory sa) + { + if (_dragonBeamCrystalTriggered) return; + _dragonBeamCrystalTriggered = true; + if (isDebug) sa.Log.Debug($"水晶实时距离计算,范围显示开启"); + _crystalCloseGuid = sa.Method.RegistFrameworkUpdateAction(CrystalCloseFrameworkAction); + } + + [ScriptMethod(name: "Wyvern's Weal - Init Other Framworks", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43937)$"], + userControl: Debugging, suppress: 2000)] + public void DragonBeamCrystalFwRefresh(Event ev, ScriptAccessory sa) + { + _crossExaflareCrystalTriggered = false; + _edgeExaflareCrystalTriggered = false; + } + + [ScriptMethod(name: "P2锁刃敲打 - P2 Chainblade Blow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45083|45086)$"])] + public void P2ChainbladeBlow(Event ev, ScriptAccessory sa) + { + var spos = ev.SourcePosition; + var srot = ev.SourceRotation; + + switch (ev.ActionId) + { + // 45086 boss right first + case 45083: + { + string msg = language == Language.Chinese ? "左侧 →→ 右侧" : "Left →→ Right"; + string msg1 = language == Language.Chinese ? "先左侧再右侧" : "Left then Right"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + + float rightX = spos.X - 20 * MathF.Sin(srot) + 7 * MathF.Cos(srot); + float rightZ = spos.Z - 20 * MathF.Cos(srot) - 7 * MathF.Sin(srot); + Vector3 rightPos = new Vector3(rightX, spos.Y, rightZ); + + float leftX = spos.X - 20 * MathF.Sin(srot) - 7 * MathF.Cos(srot); + float leftZ = spos.Z - 20 * MathF.Cos(srot) + 7 * MathF.Sin(srot); + Vector3 leftPos = new Vector3(leftX, spos.Y, leftZ); + + DrawHelper.DrawRectPosNoTarget(sa, rightPos, new Vector2(28f, 80f), srot, 6700, + $"Chainblade Blow right-{ev.SourceId}", sa.Data.DefaultDangerColor); + + DrawHelper.DrawRectPosNoTarget(sa, leftPos, new Vector2(28f, 80f), srot, 3500, + $"Chainblade Blow left-{ev.SourceId}", sa.Data.DefaultDangerColor, delay: 6700); + break; + } + // 45083 boss left first + case 45086: + { + string msg = language == Language.Chinese ? "左侧 ←← 右侧" : "Left ←← Right"; + string msg1 = language == Language.Chinese ? "先右侧再左侧" : "Right then Left"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + + float rightX = spos.X - 20 * MathF.Sin(srot) + 7 * MathF.Cos(srot); + float rightZ = spos.Z - 20 * MathF.Cos(srot) - 7 * MathF.Sin(srot); + Vector3 rightPos = new Vector3(rightX, spos.Y, rightZ); + + float leftX = spos.X - 20 * MathF.Sin(srot) - 7 * MathF.Cos(srot); + float leftZ = spos.Z - 20 * MathF.Cos(srot) + 7 * MathF.Sin(srot); + Vector3 leftPos = new Vector3(leftX, spos.Y, leftZ); + + DrawHelper.DrawRectPosNoTarget(sa, leftPos, new Vector2(28f, 80f), srot, 6700, + $"Chainblade Blow left-{ev.SourceId}", sa.Data.DefaultDangerColor); + + DrawHelper.DrawRectPosNoTarget(sa, rightPos, new Vector2(28f, 80f), srot, 3500, + $"Chainblade Blow right-{ev.SourceId}", sa.Data.DefaultDangerColor, delay: 6700); + break; + } + } + } + + + + + [ScriptMethod(name: "P2锁刃飞翔突进【龙闪】 - P2 Wyvern's Siegeflight", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45071|45072)$"])] + public void P2WyvernsSiegeflight(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 45071 small + // 45072 split + case 45071: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 6200, $"Wyvern's Siegeflight mid-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha)); + break; + } + case 45072: + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3400, $"Wyvern's Siegeflight Left-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(12f, 0, 0f), delay: 6600); + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 40f), 3400, $"Wyvern's Siegeflight Right-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(-12f, 0, 0f), delay: 6600); + break; + } + } + } + + [ScriptMethod(name: "P2回旋锁刃【龙闪】 - P2 Wyvern's Ouroblade", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45107|45108)$"])] + public void P2WyvernsOuroblade(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 45107 left + // 45108 right + case 45107: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, float.Pi / 2, new Vector2(40f), 180, 6700, $"Wyvern's Ouroblade left-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + case 45108: + { + DrawHelper.DrawFanObject(sa, ev.SourceId, -float.Pi / 2, new Vector2(40f), 180, 6700, $"Wyvern's Ouroblade right-{ev.SourceId}", sa.Data.DefaultDangerColor, + scaleByTime: false); + break; + } + } + } + + [ScriptMethod(name: "龙尾突刺 - Steeltail Thrust", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43949|44806)$"])] + public void SteeltailThrust(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTargetWithRot(sa, ev.SourceId, new Vector2(6f, 30f), float.Pi, 3300, $"Steeltail Thrust-{ev.SourceId}", sa.Data.DefaultDangerColor); + string msg = language == Language.Chinese ? "远离背后" : "Avoid behind the boss"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + public Vector3 WrathfulRattlePos = new Vector3(0, 0, 0); + public float WrathfulRattleRot = 0f; + [ScriptMethod(name: "锁哭龙闪·改 - Wrathful Rattle", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43944)$"])] + public void WrathfulRattle(Event ev, ScriptAccessory sa) + { + DebugMsg($"In WrathfulRattle", sa); + lock (CountLock) + { + var spos = ev.SourcePosition; + WrathfulRattlePos = spos; + var srot = ev.SourceRotation; + WrathfulRattleRot = srot; + + int[] delays = { 3200, 6200, 9200, 12200 }; + int[] durations = { 3000, 3000, 3000, 3000 }; + + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 3200, $"Wrathful Rattle main-{ev.SourceId}", sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 20)); + + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 6f + i * 4f; + + float rightX = WrathfulRattlePos.X - 20 * MathF.Sin(WrathfulRattleRot) + sideDist * MathF.Cos(WrathfulRattleRot); + float rightZ = WrathfulRattlePos.Z - 20 * MathF.Cos(WrathfulRattleRot) - sideDist * MathF.Sin(WrathfulRattleRot); + Vector3 rightPos = new Vector3(rightX, WrathfulRattlePos.Y, rightZ); + + + float leftX = WrathfulRattlePos.X - 20 * MathF.Sin(WrathfulRattleRot) - sideDist * MathF.Cos(WrathfulRattleRot); + float leftZ = WrathfulRattlePos.Z - 20 * MathF.Cos(WrathfulRattleRot) + sideDist * MathF.Sin(WrathfulRattleRot); + Vector3 leftPos = new Vector3(leftX, WrathfulRattlePos.Y, leftZ); + + DrawHelper.DrawRectPosNoTarget(sa, rightPos, new Vector2(4f, 40f), WrathfulRattleRot, durations[i], + $"Wrathful Rattle right-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + + DrawHelper.DrawRectPosNoTarget(sa, leftPos, new Vector2(4f, 40f), WrathfulRattleRot, durations[i], + $"Wrathful Rattle left-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + } + } + } + + [ScriptMethod(name: "锁哭龙闪·改(折返) - Wrathful Rattle Return", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43946)$"])] + public void WrathfulRattleReturn(Event ev, ScriptAccessory sa) + { + lock (CountLock) + { + var Epos = new Vector3(118.00f, 0.00f, 100.00f); + var Wpos = new Vector3(82.00f, 0.00f, 100.00f); + var Npos = new Vector3(100.00f, 0.00f, 82.00f); + var Spos = new Vector3(100.00f, 0.00f, 118.00f); + + var spos = ev.SourcePosition; + var srot = ev.SourceRotation; + + int[] delays = { 2800, 4500, 7000, 9500, 12000, 15000, 17500, 20000, 22500 }; + int[] durations = { 1700, 1700, 2500, 2500, 3000, 2500, 2500, 2500, 2500 }; + + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8f, 40f), 2800, $"Wrathful Rattle main-{ev.SourceId}", + sa.Data.DefaultDangerColor, offset: new Vector3(0, 0, 20)); + // E to W + if (Vector3.Distance(spos, Epos) < 10) + { + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 4f + i * 4f; + + float X = spos.X + sideDist * MathF.Cos(srot); + float Z = spos.Z - 20 * MathF.Cos(srot) + sideDist * MathF.Sin(srot); + Vector3 Pos = new Vector3(X, spos.Y, Z); + + DrawHelper.DrawRectPosNoTarget(sa, Pos, new Vector2(4f, 40f), srot, durations[i], + $"Wrathful Rattle right-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + DebugMsg($"Pos: {Pos}, srot: {srot}, MathF.Cos(srot) {MathF.Cos(srot)}", sa); + } + } // W to E + else if (Vector3.Distance(spos, Wpos) < 10) + { + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 4f + i * 4f; + + float X = spos.X - sideDist * MathF.Cos(srot); + float Z = spos.Z - 20 * MathF.Cos(srot) + sideDist * MathF.Sin(srot); + Vector3 Pos = new Vector3(X, spos.Y, Z); + + DrawHelper.DrawRectPosNoTarget(sa, Pos, new Vector2(4f, 40f), srot, durations[i], + $"Wrathful Rattle left-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + } + } // S to N + else if (Vector3.Distance(spos, Spos) < 10) + { + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 4f + i * 4f; + + float X = spos.X - 20 * MathF.Sin(srot) + sideDist * MathF.Cos(srot); + float Z = spos.Z - 20 * MathF.Cos(srot) + sideDist * MathF.Sin(srot); + Vector3 Pos = new Vector3(X, spos.Y, Z); + + DrawHelper.DrawRectPosNoTarget(sa, Pos, new Vector2(4f, 40f), srot, durations[i], + $"Wrathful Rattle S-N-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + } + } // N to S + else if (Vector3.Distance(spos, Npos) < 10) + { + for (int i = 0; i < delays.Length; i++) + { + float sideDist = 4f + i * 4f; + + float X = spos.X - 20 * MathF.Sin(srot) - sideDist * MathF.Cos(srot); + float Z = spos.Z - 20 * MathF.Cos(srot) - sideDist * MathF.Sin(srot); + Vector3 Pos = new Vector3(X, spos.Y, Z); + + DrawHelper.DrawRectPosNoTarget(sa, Pos, new Vector2(4f, 40f), srot, durations[i], + $"Wrathful Rattle N-S-{ev.SourceId}-{i}", + color: new Vector4(1, 0, 0, ColorAlpha), delay: delays[i]); + } + } + } + } + + [ScriptMethod(name: "P2场边波状龙闪 - P2 Arena Edge Wyvern's Vengeance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43952)$"])] + public void P2WyvernsVengeance(Event ev, ScriptAccessory sa) + { + var startPos = ev.EffectPosition; + var srot = ev.SourceRotation; + + float moveDistance = 8f; + //int[] durations = { 7700, 1300, 1300, 1300, 1300, 1300 }; + //int[] delays = { 0, 7700, 9000, 10300, 11600, 12900 }; + int[] durations = { 7700, 2700, 2700, 2700, 2700, 2700 }; + int[] delays = { 0, 6400, 7700, 9000, 10300, 11600 }; + + for (int i = 0; i < 6; i++) + { + float X = startPos.X + moveDistance * i * MathF.Sin(srot); + float Z = startPos.Z + moveDistance * i * MathF.Cos(srot); + Vector3 drawPos = new Vector3(X, startPos.Y, Z); + + DrawHelper.DrawCircle(sa, drawPos, new Vector2(6), durations[i], + $"P2 Wyvern's Vengeance-{i}-{ev.SourceId}", + color: new Vector4(1, 0, 0, ColorAlpha), + scaleByTime: false, + delay: delays[i]); + } + } + + [ScriptMethod(name: "P2场边波状龙闪惰性水晶绘制 - P2 Arena Edge Wyvern's Vengeance Cracked Crystal Drawing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43952)$"], + userControl: true, suppress: 2000)] + public void EdgeExaflareCrystalFw(Event ev, ScriptAccessory sa) + { + if (_edgeExaflareCrystalTriggered) return; + + _edgeExaflareCrystalTriggered = true; + sa.Log.Debug($"水晶实时距离计算,范围显示开启"); + _crystalCloseGuid = sa.Method.RegistFrameworkUpdateAction(CrystalCloseFrameworkAction); + } + + [ScriptMethod(name: "P2 Arena Edge Wyvern's Vengeance - Init Other Framworks", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43952)$"], + userControl: Debugging, suppress: 2000)] + public void EdgeExaflareCrystalFwRefresh(Event ev, ScriptAccessory sa) + { + _crossExaflareCrystalTriggered = false; + _dragonBeamCrystalTriggered = false; + } + + [ScriptMethod(name: "P2钢铁 - P2 Chariot", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43933)$"])] + public void P2Chariot(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(12f), 4700, + $"P2 Chariot-{ev.SourceId}", + sa.Data.DefaultDangerColor); + + } + + #endregion + #region Framework + private void CrystalCloseFrameworkAction() + { + ScriptAccessory sa = _sa; + var closeDistance = 2f; + + var myobj = sa.Data.MyObject; + if (myobj is null) return; + var myPos = myobj.Position; + + lock (_crystals) + { + if (_crystals.Count == 0) + { + if (isDebug) sa.Log.Debug($"水晶数量归零,清除"); + sa.Method.UnregistFrameworkUpdateAction(_crystalCloseGuid); + } + + foreach (var crystal in _crystals) + { + var distance = Vector3.Distance(crystal.Obj.Position, myPos); + var isSmallCrystal = crystal.Obj.DataId == 18662; + var crystalRange = isSmallCrystal ? 6 : 12; + bool isClose = distance < crystalRange + closeDistance; + + if (isClose && !crystal.Drawn) + { + if (isDebug) sa.Log.Debug($"玩家距离水晶{crystal.Obj.GameObjectId}近,显示绘图,范围{crystalRange}"); + DrawHelper.DrawCircleObject(sa, crystal.Obj.GameObjectId, new Vector2(crystalRange), 60000, $"水晶-{crystal.Obj.GameObjectId}", + new Vector4(0, 1, 1, ColorAlpha), scaleByTime: false); + crystal.Drawn = true; + } + else if (!isClose && crystal.Drawn) + { + if (isDebug) sa.Log.Debug($"玩家距离水晶{crystal.Obj.GameObjectId}远,删除绘图"); + sa.Method.RemoveDraw($"水晶-{crystal.Obj.GameObjectId}"); + crystal.Drawn = false; + } + } + } + } + #endregion + + + [ScriptMethod(name: "Unit - Remove & Refresh ALL", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:73"], + userControl: Debugging)] + public void Uninit(Event ev, ScriptAccessory sa) + { + if (isDebug) sa.Log.Debug($"Uninit Triggered"); + sa.Method.RemoveDraw(".*"); + _crystals.RemoveAll(x => x.Obj.GameObjectId == ev.SourceId); + RefreshParams(); + + sa.Method.ClearFrameworkUpdateAction(this); + } + + + #region 优先级字典 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 + +} + + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战) / "Ranged DPS"(远程) / "Unknown" / "None" + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + return playerObject.ClassJob.Value.Role switch + { + 1 => "Tank", // 坦克 + 4 => "Healer", // 治疗 + 2 => "Melee DPS", // 近战DPS + 3 => "Ranged DPS", // 远程DPS + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + public static string GetObjectName(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return "None"; + return obj.Name.ToString(); + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } + + [Flags] + public enum DrawState : uint + { + Invisibility = 0x00_00_00_02, + IsLoading = 0x00_00_08_00, + SomeNpcFlag = 0x00_00_01_00, + MaybeCulled = 0x00_00_04_00, + MaybeHiddenMinion = 0x00_00_80_00, + MaybeHiddenSummon = 0x00_80_00_00, + } + + public static unsafe DrawState* ActorDrawState(IGameObject actor) + => (DrawState*)(&((GameObject*)actor.Address)->RenderFlags); + + /// + /// 检查对象可见性(Read) + /// + /// ScriptAccessory + /// Obj need check + /// true=检查可见,false=检查不可见 + /// 如果符合检查条件返回 True + public static unsafe bool IsActorVisible(this ScriptAccessory sa, IGameObject? obj, bool checkVisible = true) + { + if (obj == null) return false; + + try + { + var state = *ActorDrawState(obj); + bool isVisible = (state & DrawState.Invisibility) == 0; + + return checkVisible ? isVisible : !isVisible; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } + + /// + /// 设置对象可见性(Write) + /// + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + if (actor == null) return; + + try + { + var statePtr = ActorDrawState(actor); + if (visible) + *statePtr &= ~DrawState.Invisibility; + else + *statePtr |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + throw; + } + } + + /// + /// Check DrawState + /// + public static unsafe bool HasDrawState(this ScriptAccessory sa, IGameObject? actor, DrawState state) + { + if (actor == null) return false; + + try + { + return (*ActorDrawState(actor) & state) != 0; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float? rotation = null, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosTarget(ScriptAccessory accessory, Vector3 pos, Vector3 targetpos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.TargetPosition = targetpos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 + +#region 脚本版本检查 +public static class ScriptVersionChecker +{ + private const string OnlineRepoUrl = "https://raw.githubusercontent.com/VeeverSW/Kodakku-Script/refs/heads/main/OnlineRepo.json"; + private static readonly HttpClient _httpClient = new HttpClient(); + + /// + /// 在线仓库脚本信息 + /// + public class OnlineScriptInfo + { + public string Name { get; set; } = ""; + public string Guid { get; set; } = ""; + public string Version { get; set; } = ""; + public string Author { get; set; } = ""; + public string Repo { get; set; } = ""; + public string DownloadUrl { get; set; } = ""; + public string Note { get; set; } = ""; + public string UpdateInfo { get; set; } = ""; + public int[] TerritoryIds { get; set; } = Array.Empty(); + } + + /// + /// 版本 + /// + public enum VersionCompareResult + { + /// 当前版本较新或相同 + UpToDate, + /// 有新版本可用 + UpdateAvailable, + /// 未找到匹配的脚本 + NotFound, + /// 检查失败 + Error + } + + /// + /// 检查脚本版本 + /// + /// ScriptAccessory + /// 脚本GUID + /// 当前版本号 + /// 是否显示通知 + /// 版本比较结果 + public static async Task<(VersionCompareResult result, OnlineScriptInfo? onlineInfo)> CheckVersionAsync( + ScriptAccessory sa, + string guid, + string currentVersion, + bool showNotification = true) + { + try + { + sa.Log.Debug($"开始检查脚本版本 (GUID: {guid}, 当前版本: {currentVersion})"); + + var response = await _httpClient.GetStringAsync(OnlineRepoUrl); + var onlineScripts = JsonConvert.DeserializeObject>(response); + + if (onlineScripts == null || onlineScripts.Count == 0) + { + sa.Log.Error("无法解析在线仓库数据"); + return (VersionCompareResult.Error, null); + } + + var onlineScript = onlineScripts.FirstOrDefault(s => + s.Guid.Equals(guid, StringComparison.OrdinalIgnoreCase)); + + if (onlineScript == null) + { + //sa.Log.Debug($"在线仓库中未找到 GUID 为 {guid} 的脚本"); + //if (showNotification) + //{ + // sa.Method.TextInfo("该脚本未在在线仓库中注册", 3000); + //} + return (VersionCompareResult.NotFound, null); + } + + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 找到在线脚本: {onlineScript.Name}, 在线版本: {onlineScript.Version}"); + + var compareResult = CompareVersions(currentVersion, onlineScript.Version); + + if (compareResult < 0) + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新 (当前: {currentVersion})"); + if (showNotification) + { + sa.Method.TextInfo( + $"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新\n当前版本: {currentVersion}", + 5000, + true); + } + return (VersionCompareResult.UpdateAvailable, onlineScript); + } + else + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 当前版本已是最新 (当前: {currentVersion}, 在线: {onlineScript.Version})"); + + return (VersionCompareResult.UpToDate, onlineScript); + } + } + catch (HttpRequestException ex) + { + //sa.Log.Error($"网络请求失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败: 网络错误", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + catch (Exception ex) + { + //sa.Log.Error($"版本检查失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + } + + /// + /// 比较版本号 + /// + /// 版本1 (例如: "0.0.0.3") + /// 版本2 (例如: "0.0.0.5") + /// 负数: version1 < version2, 0: 相等, 正数: version1 > version2 + private static int CompareVersions(string version1, string version2) + { + var v1Parts = version1.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + var v2Parts = version2.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + + int maxLength = Math.Max(v1Parts.Length, v2Parts.Length); + + for (int i = 0; i < maxLength; i++) + { + int v1Part = i < v1Parts.Length ? v1Parts[i] : 0; + int v2Part = i < v2Parts.Length ? v2Parts[i] : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } +} +#endregion diff --git a/VeeverSW/07-DawnTrail/Vault-Oneiron.cs b/VeeverSW/07-DawnTrail/Vault-Oneiron.cs new file mode 100644 index 0000000..7c7a7d6 --- /dev/null +++ b/VeeverSW/07-DawnTrail/Vault-Oneiron.cs @@ -0,0 +1,2329 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Common.Lua; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.DawnTrail.Vault_Oneiron; + +[ScriptType(name: Name, territorys: [1279], guid: "027ed2ae-4162-4d1e-a77d-a74d9441b065", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class Vault_Oneiron +{ + const string NoteStr = + $""" + v{Version} + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "巡梦金库 [Vault Oneiron]"; + private const string Version = "0.0.0.3"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + [UserSetting("是否自动使用防击退(Auto anti-knockback)")] + public bool useAntiKnockback { get; set; } = true; + + //[UserSetting("指路开关(Guide arrow toggle)")] + //public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + //[UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + //public bool PostNamazuPrint { get; set; } = true; + + //[UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + //public int PostNamazuPort { get; set; } = 2019; + + //[UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + //public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + private readonly object CountLock = new object(); + + + private Dictionary tetherData = new Dictionary(); + private Dictionary Boss1QuadraRecordData = new Dictionary(); + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + VioletBoltCount = 0; + } + + + #region Mobs + [ScriptMethod(name: "-------- 小怪 - Mobs --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void destoryCancelAction(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($".*{ev.TargetId}"); + } + + [ScriptMethod(name: "痛打 - Batter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43602)$"])] + public void Batter(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Batter-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "Batter Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43602"], userControl: false)] + public void ElectroswipeClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Batter-{ev.SourceId}"); + } + + [ScriptMethod(name: "洪水 - Flood", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43605)$"])] + public void Flood(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 2700, $"Flood-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Flood Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43605"], userControl: false)] + public void FloodClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Flood-{ev.SourceId}"); + } + + [ScriptMethod(name: "狂水 - Water III", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43606)$"])] + public void WaterIII(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 3700, $"WaterIII-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Water III Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43606"], userControl: false)] + public void WaterIIIClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"WaterIII-{ev.SourceId}"); + } + + //[ScriptMethod(name: "万变水波 - Protean Wave", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43609)$"])] + //public void ProteanWave(Event ev, ScriptAccessory sa) + //{ + // DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(39f), 30, 3700, $"ProteanWave-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + //} + + [ScriptMethod(name: "AOES", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43608|43633|43626|43616)$"])] + public void MobsAOES(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43607|43638|43615)$"])] + public void MobsTankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "盐水炸弹 - Brine Bomb", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43610)$"])] + public void BrineBomb(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(5f), 2700, $"BrineBomb-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "BrineBomb Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43610"], userControl: false)] + public void BrineBombClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"BrineBomb-{ev.SourceId}"); + } + + [ScriptMethod(name: "冷气 - Bitter Chill", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43631)$"])] + public void BitterChill(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"BitterChill-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Bitter Chill Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43631"], userControl: false)] + public void BitterChillClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"BitterChill-{ev.SourceId}"); + } + + [ScriptMethod(name: "坦克AOE死刑提示 - Tank AOE buster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43630|43623)$"])] + public void TankBusterMobs(Event ev, ScriptAccessory sa) + { + string msg = ""; + if (ev.TargetId == sa.Data.Me) + { + msg = language == Language.Chinese ? "直线AOE死刑, 注意减伤" : "Tankbuster, Use mits"; + } + else + { + msg = language == Language.Chinese ? "远离范围AOE死刑" : "AOE Tankbuster — Stay Away"; + } + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + + DrawHelper.DrawRectObjectTarget(sa, ev.SourceId, ev.TargetId, new Vector2(8f, 60f), 4700, $"TankBuster-{ev.TargetId}", color: new Vector4(1, 0, 0, ColorAlpha)); + } + + [ScriptMethod(name: "自控射击 - Homing Shot", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43635)$"])] + public void HomingShot(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Homing Shot-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Homing Shot Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43635"], userControl: false)] + public void HomingShotClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Homing Shot-{ev.SourceId}"); + } + + [ScriptMethod(name: "汽油弹 - Thrown Flames", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43636)$"])] + public void ThrownFlames(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 2700, $"ThrownFlames-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Thrown Flames Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43636"], userControl: false)] + public void ThrownFlamesClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"ThrownFlames-{ev.SourceId}"); + } + + [ScriptMethod(name: "根系纠缠 - Entangle", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43621)$"])] + public void Entangle(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(4f), 2700, $"Entangle-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Entangle Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43621"], userControl: false)] + public void EntangleClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Entangle-{ev.SourceId}"); + } + + [ScriptMethod(name: "狂风 - Gust", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43624)$"])] + public void Gust(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Gust-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Gust Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43624"], userControl: false)] + public void GustClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Gust-{ev.SourceId}"); + } + + [ScriptMethod(name: "束风 - Whipwind", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43625)$"])] + public void Whipwind(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(40f, 60f), 5700, + $"Whipwind-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scalemode: ScaleMode.ByTime); + } + + [ScriptMethod(name: "Whipwind Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43625"], userControl: false)] + public void WhipwindClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Whipwind-{ev.SourceId}"); + } + + [ScriptMethod(name: "炎丝喷射 - Molten Silk", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43613)$"])] + public void MoltenSilk(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, float.Pi, new Vector2(9f), 270, 2700, $"Molten Silk-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Molten Silk Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43613"], userControl: false)] + public void MoltenSilkClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Molten Silk-{ev.SourceId}"); + } + + [ScriptMethod(name: "飞跃重压 - Flying Press", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43614)$"])] + public void FlyingPress(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Flying Press-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Flying Press Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43614"], userControl: false)] + public void FlyingPressClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Flying Press-{ev.SourceId}"); + } + + [ScriptMethod(name: "挑起 - Scoop", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43618)$"])] + public void Scoop(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(13f), 120, 3700, $"Scoop-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Scoop Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43618"], userControl: false)] + public void ScoopClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Scoop-{ev.SourceId}"); + } + + [ScriptMethod(name: "回转 - Spin", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43619)$"])] + public void Spin(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(11f), 3200, $"Spin-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Spin Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43619"], userControl: false)] + public void SpinClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Spin-{ev.SourceId}"); + } + + [ScriptMethod(name: "回扫枪 - Lance Swing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43726)$"])] + public void LanceSwing(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 3700, $"Lance Swing-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "Lance Swing Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:43726"], userControl: false)] + public void LanceSwingClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Lance Swing-{ev.SourceId}"); + } + + #endregion + + #region the vault vegetators + [ScriptMethod(name: "-------- 金库蔓德拉战队 - the Vault Vegetators --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void vaultVegetators(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "AOES", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(32305|32303|32302|32304|32301)$"])] + public void AOES(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(7f), 3200, $"UpliftSequence-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + #endregion + + #region Fastitocalon + [ScriptMethod(name: "-------- 法斯提托卡伦 - Fastitocalon --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Fastitocalon(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "地震 - Tremblor", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43664)$"])] + public void Tremblor(Event ev, ScriptAccessory sa) + { + //string msg = language == Language.Chinese ? "二穿一躲避" : "Dodge between two rings"; + //if (isText) sa.Method.TextInfo($"{msg}", duration: 4200, true); + //if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(10f), 3700, $"Tremblor-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + DrawHelper.DrawDountObject(sa, ev.TargetId, new Vector2(20f), new Vector2(10f), 3700, $"Tremblor1-{ev.SourceId}", delay: 3000, color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + DrawHelper.DrawDountObject(sa, ev.TargetId, new Vector2(30f), new Vector2(20f), 3700, $"Tremblor2-{ev.SourceId}", delay: 6000, color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(10f), 7000, $"TremblorSafe-{ev.SourceId}", delay: 3700, color: new Vector4(0, 1, 0, 0.5f), scaleByTime: false); + DrawHelper.DrawDountObject(sa, ev.TargetId, new Vector2(20f), new Vector2(10f), 3700, $"TremblorSafe1-{ev.SourceId}", delay: 6700, color: new Vector4(0, 1, 0, 0.5f), scaleByTime: false); + DrawHelper.DrawDountObject(sa, ev.TargetId, new Vector2(30f), new Vector2(20f), 1000, $"TremblorSafe2-{ev.SourceId}", delay: 9700, color: new Vector4(0, 1, 0, 0.5f), scaleByTime: false); + } + + [ScriptMethod(name: "连锁隆起 - Uplift Sequence", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43659|43660|43661|43662)$"])] + public void UpliftSequence(Event ev, ScriptAccessory sa) + { + // 43659 4700 + // 43660 6700 + // 43661 8700 + // 43662 10700 + var time = 4700; + if (ev.ActionId == 43660) time = 6700; + else if (ev.ActionId == 43661) time = 8700; + else if (ev.ActionId == 43662) time = 10700; + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), time, $"UpliftSequence-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43668"])] + public void FastitocalonTankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } else + { + string msg = language == Language.Chinese ? "远离范围死刑" : "Avoid AOE tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43672"])] + public void FastitocalonAOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "地盘震动 - Earthshake", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43670)$"])] + public void Earthshake(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Earthshake-{ev.SourceId}", color: new Vector4(1, 1, 0, ColorAlpha), scaleByTime: true); + } + #endregion + + #region great gimme cat + [ScriptMethod(name: "-------- 超级索取猫 - great gimme cat --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void greatgimmecat(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "闪闪三角喵 - Preening Prism", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43687|43688)$"])] + public void PreeningPrism(Event ev, ScriptAccessory sa) + { + // 43687 5700 + // 43688 7700 + var delay = 0; + var time = 5700; + if (ev.ActionId == 43688) + { + delay = 5700; + time = 2000; + } + DrawHelper.DrawFanObject(sa, ev.SourceId, MathTools.DegToRad(0f), new Vector2(14f), 60, time, + $"PreeningPrismOrigin-{ev.SourceId}", color: sa.Data.DefaultDangerColor, drawmode: DrawModeEnum.Imgui, scaleByTime: false, delay: delay); + DrawHelper.DrawFanObject(sa, ev.SourceId, MathTools.DegToRad(112.5f), new Vector2(20f), 45, time, + $"PreeningPrismZoffset-{ev.SourceId}", color: sa.Data.DefaultDangerColor, offset: new Vector3(14.1f, 0, -14.1f), drawmode: DrawModeEnum.Imgui, scaleByTime: false, delay: delay); + DrawHelper.DrawFanObject(sa, ev.SourceId, MathTools.DegToRad(-112.5f), new Vector2(20f), 45, time, + $"PreeningPrismXoffset-{ev.SourceId}", color: sa.Data.DefaultDangerColor, offset: new Vector3(-14.1f, 0, -14.1f), drawmode: DrawModeEnum.Imgui, scaleByTime: false, delay: delay); + } + + [ScriptMethod(name: "闪闪光线喵 - BaskingBeam", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43690)$"])] + public void BaskingBeam(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(5f, 40f), 2700, + $"BaskingBeam-{ev.SourceId}", color: new Vector4(1, 1, 0, ColorAlpha), offset: new Vector3(0, 0, 20)); + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43698"])] + public void GimmeTankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? "远离范围死刑" : "Avoid AOE tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "闪闪圆圈喵 - Glitterbox", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43692)$"])] + public void Glitterbox(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(5f), 2700, $"Glitterbox-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: true); + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43672"])] + public void GimmeAOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + #endregion + + #region Old Bitter-eye + + [ScriptMethod(name: "-------- 苦眼 - Old Bitter-eye --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void OldBitterEye(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "十星石回转 - 10-stone Swing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43675)$"])] + public void TenStoneSwing(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(11f), 3700, $"10-stone Swing-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "百星石重击 - 100-stone Swipe", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43678)$"])] + public void OoStoneSwipe(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(40f), 60, 3700, $"100-stone Swipe-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "飞扑 - Laughing Leap", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43683)$"])] + public void LaughingLeap(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 3700, $"Laughing Leap-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "雷电之眼 - Eye of the Thunderstorm", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43680)$"])] + public void EyeoftheThunderstorm(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "进入月环" : "Move in (Dynamo)"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(5f), 3700, $"Eye of the Thunderstorm Safe-{ev.SourceId}", + color: sa.Data.DefaultSafeColor, scaleByTime: false); + + DrawHelper.DrawDountObject(sa, ev.SourceId, new Vector2(40f), new Vector2(5f), 3700, $"Eye of the Thunderstorm Danger-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "千星石回转 - 1000-stone Swing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43677)$"])] + public void OoostoneSwing(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "打断Boss" : "Interrupt!"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 7700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "百星石回转 - 100-stone Swing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43676)$"])] + public void OostoneSwing(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(13f), 1200, $"100-stone Swing-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "掠夺本能防击退 - Predatorial Instinct Anti-KnockBack", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43684)$"])] + public void PredatorialInstinct(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "远离Boss" : "Away from Boss"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(13f), 7700, $"100-stone Swing-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + + if (useAntiKnockback) + { + sa.Method.UseAction(sa.Data.Me, 7559); + sa.Method.UseAction(sa.Data.Me, 7548); + } + } + + [ScriptMethod(name: "坦克AOE死刑提示 - Tank AOE buster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43682)$"])] + public void OldBitterEyeTankBusterMobs(Event ev, ScriptAccessory sa) + { + string msg = ""; + if (ev.TargetId == sa.Data.Me) + { + msg = language == Language.Chinese ? "直线AOE死刑, 注意减伤" : "Tankbuster, Use mits"; + } + else + { + msg = language == Language.Chinese ? "远离范围AOE死刑" : "AOE Tankbuster — Stay Away"; + } + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + + DrawHelper.DrawRectObjectTarget(sa, ev.SourceId, ev.TargetId, new Vector2(8f, 65f), 4700, $"TankBuster-{ev.TargetId}", color: new Vector4(1, 0, 0, ColorAlpha)); + } + #endregion + + #region paeonia + [ScriptMethod(name: "-------- 培奥尼亚 - paeonia --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void paeonia(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "落雷 - Lightning Bolt", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43704)$"])] + public void LightningBolt(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Lightning Bolt-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + [ScriptMethod(name: "凝视 - Hypnotize", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43702)$"])] + public async void Hypnotize(Event ev, ScriptAccessory sa) + { + await Task.Delay(4000); + string msg = language == Language.Chinese ? "背对Boss" : "Face Away from Boss"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "环状放雷 - Levinroot Ring", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43713|43710)$"])] + public void LevinrootRing(Event ev, ScriptAccessory sa) + { + if (ev.ActionId == 43713) + { + string msg = language == Language.Chinese ? "先靠近,后远离" : "Dynamo then Chariot"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, $"Levinroot Ring Safe-{ev.SourceId}", + color: sa.Data.DefaultSafeColor, scaleByTime: false); + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 3000, $"Levinroot Ring Danger-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: false, delay: 4700); + } else + { + string msg = language == Language.Chinese ? "先远离,后靠近" : "Chariot then Dynamo"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, $"Levinroot Ring Danger-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: false); + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 3000, $"Levinroot Ring Safe-{ev.SourceId}", + color: sa.Data.DefaultSafeColor, scaleByTime: false, delay: 4700); + + } + } + + [ScriptMethod(name: "扇状放雷 - Lightning Crossing", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43706|43707)$"])] + public void LightningCrossing(Event ev, ScriptAccessory sa) + { + // 43707 6700 + // 43706 4700 + if (ev.ActionId == 43706) + { + DrawHelper.DrawFanObjectNoRot(sa, ev.SourceId, new Vector2(40f), 45, 4700, + $"Lightning Crossing Danger-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + + DrawHelper.DrawFanObjectNoRot(sa, ev.SourceId, new Vector2(40f), 45, 2000, + $"Lightning Crossing Safe-{ev.SourceId}", color: sa.Data.DefaultSafeColor, delay: 4700); + } + else + { + DrawHelper.DrawFanObjectNoRot(sa, ev.SourceId, new Vector2(40f), 45, 4700, + $"Lightning Crossing Safe-{ev.SourceId}", color: sa.Data.DefaultSafeColor); + + DrawHelper.DrawFanObjectNoRot(sa, ev.SourceId, new Vector2(40f), 45, 2000, + $"Lightning Crossing Danger-{ev.SourceId}", color: sa.Data.DefaultDangerColor, delay: 4700); + } + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43715"])] + public void paeoniaAOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43716"])] + public void paeoniaTankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + #endregion + + #region Gwyddneu + + [ScriptMethod(name: "-------- 格威迪恩 - Gwyddneu --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Gwyddneu(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43648|43654)$"])] + public void GwyddneuAOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "落雷 - Lightning Bolt", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43652)$"])] + public void GwyddneuLightningBolt(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Lightning Bolt-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + public int VioletBoltCount = 0; + [ScriptMethod(name: "紫电 - Violet Bolt", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43645)$"])] + public async void VioletBolt(Event ev, ScriptAccessory sa) + { + var delay = 0; + var duration = 4700; + lock (CountLock) + { + if (VioletBoltCount > 3) + { + delay = 2700; + duration = 2000; + } + DrawHelper.DrawFanObjectNoRot(sa, ev.SourceId, new Vector2(70f), 45, duration, $"VioletBolt-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, delay: delay); + sa.Log.Debug($"VioletBolt Count: {VioletBoltCount}"); + VioletBoltCount++; + } + + await Task.Delay(10000); + VioletBoltCount = 0; + + } + + [ScriptMethod(name: "放电 - Shock", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(18599|18598)$"])] + public void Shock(Event ev, ScriptAccessory sa) + { + // 18598 small + // 18599 big + sa.Log.Debug($"Shock DataId: {ev.DataId()}"); + switch (ev.DataId()) + { + case 18598: + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(8f), 120000, $"Shock-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: false); + break; + case 18599: + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(16f), 120000, $"Shock-{ev.SourceId}", + color: sa.Data.DefaultDangerColor, scaleByTime: false); + break; + } + + } + + [ScriptMethod(name: "Shock Remove", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43655|43656)$"])] + public async void ShockRemove(Event ev, ScriptAccessory sa) + { + await Task.Delay(2700); + sa.Method.RemoveDraw($"Shock-{ev.SourceId}"); + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43650"])] + public void GwyddneuTankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? "远离范围死刑" : "Avoid AOE tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "GwyddneuClear", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:regex:^(9020)$"], userControl: false)] + public void GwyddneuClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw(".*"); + } + + #endregion + + #region gilded sentry + [ScriptMethod(name: "-------- 黄金哨兵 - gilded sentry --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void gildedSentry(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "十字雷 - Cross Lightning", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43719)$"])] + public void CrossLightning(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 100f), 4700, + $"Cross Lightning-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha), offset: new Vector3(0, 0, 50)); + + DrawHelper.DrawRectObjectNoTargetWithRot(sa, ev.SourceId, new Vector2(10f, 100f), float.Pi / 2, 4700, + $"Cross Lightning-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha), offset: new Vector3(50, 0, 0)); + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43723)$"])] + public void gildedSentryAOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "王国闪雷 - Alexandrian Thunder", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43721)$"])] + public void AlexandrianThunder(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Alexandrian Thunder-{ev.SourceId}", color: sa.Data.DefaultDangerColor, scaleByTime: true); + } + + + #endregion + + #region Spirit of Thunder + [ScriptMethod(name: "-------- 雷电之魂 - Spirit of Thunder --------", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void SpiritofThunder(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "雷转质射线 - Electray", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43773|43774)$"])] + public void Electray(Event ev, ScriptAccessory sa) + { + // 43774 7700 + // 43773 4700 + // V2(10, 20) + var delay = 0; + var duration = 4700; + if (ev.ActionId == 43774) + { + delay = 5000; + duration = 2700; + } + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 20f), duration, + $"Cross Lightning-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha), delay: delay); + + if (ev.ActionId == 43773) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 20f), 2700, + $"Cross Lightning-{ev.SourceId}", color: sa.Data.DefaultSafeColor, delay: 4700); + } + } + + [ScriptMethod(name: "死刑提示 - Tankbuster Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43784"])] + public void SpiritofThunderTankbuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "死刑点名" : "Targeted Buster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? "远离范围死刑" : "Avoid AOE tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4500, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43785)$"])] + public void SpiritofThunderAOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4200, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "高能雷转质射线 - High-voltage Electray", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43778|43779|43780|43781)$"])] + public void HighVoltageElectray(Event ev, ScriptAccessory sa) + { + // 43778 4700 + // 43779 5700 + // 43780 6700 + // 43781 7700 + switch (ev.ActionId) + { + case 43778: + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 20f), 4700, + $"High-voltage Electray 0-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha), delay: 0, offset: new Vector3(0, 0, 10)); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 20f), 3000, + $"High-voltage Electray Safe 0-{ev.SourceId}", color: sa.Data.DefaultSafeColor, delay: 4700, offset: new Vector3(0, 0, 10)); + break; + case 43779: + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 20f), 1000, + $"High-voltage Electray 1-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha), delay: 4700, offset: new Vector3(0, 0, 10)); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 20f), 2000, + $"High-voltage Electray Safe 1-{ev.SourceId}", color: sa.Data.DefaultSafeColor, delay: 5700, offset: new Vector3(0, 0, 10)); + break; + case 43780: + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 20f), 1000, + $"High-voltage Electray 2-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha), delay: 5700, offset: new Vector3(0, 0, 10)); + + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 20f), 1000, + $"High-voltage Electray Safe 2-{ev.SourceId}", color: sa.Data.DefaultSafeColor, delay: 6700, offset: new Vector3(0, 0, 10)); + break; + case 43781: + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20f, 20f), 1000, + $"High-voltage Electray 3-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha), delay: 6700, offset: new Vector3(0, 0, 10)); + break; + } + } + + [ScriptMethod(name: "高压放雷 - Power Line", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(18614)$"])] + public void PowerLine(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(10f, 40f), 120000, + $"Power Line-{ev.SourceId}", color: new Vector4(1, 0, 1, ColorAlpha)); + } + + [ScriptMethod(name: "PowerLine Remove", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43788)$"])] + public async void PowerLineRemove(Event ev, ScriptAccessory sa) + { + await Task.Delay(1000); + sa.Method.RemoveDraw($"Power Line-{ev.SourceId}"); + } + + #endregion +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 diff --git a/VeeverSW/07-DawnTrail/Yuweyawata-Field-Station.cs b/VeeverSW/07-DawnTrail/Yuweyawata-Field-Station.cs new file mode 100644 index 0000000..7d723a0 --- /dev/null +++ b/VeeverSW/07-DawnTrail/Yuweyawata-Field-Station.cs @@ -0,0 +1,1047 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.Intrinsics.Arm; +using System.Collections.Generic; +using System.Xml.Linq; +using FFXIVClientStructs; +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +namespace Veever.DawnTrail.YuweyawataFieldStation; + +[ScriptType(name: "LV.100 废弃据点玉韦亚瓦塔实验站", territorys: [1242], guid: "992e47a8-17d0-4379-891b-0762c0509257", + version: "0.0.2.2", author: "Veever", note: noteStr)] + +public class YuweyawataFieldStation +{ + //^(?!.*((武僧|机工士|龙骑士|学者|舞者|蝰蛇剑士|暗黑骑士|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast|Cancel|Add))).*$ + public int RawElectropeCount, TelltaleTearsTTSCount, LightningStormTTSCount, DarkTwoCount, JaggedEdgeCount, RockBlastNaviCount, RagingClawCount; + private readonly object LeapingEarthLock = new object(); + private readonly object JaggedEdgeLock = new object(); + public int LeapingEarthResult; + + const string noteStr = + """ + v0.0.2.2: + 1. 如果有漏画(Boss出现在南侧核爆)错画的情况,请在dc@我,并附上arr文件 + 鸭门。 + """; + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = false; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + + public async void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + LightningStormTTSCount = 0; + RawElectropeCount = 0; + TelltaleTearsTTSCount = 0; + DarkTwoCount = 0; + JaggedEdgeCount = 0; + LeapingEarthResult = 0; + RockBlastNaviCount = 0; + RagingClawCount = 0; + + await Task.Delay(50); + } + private bool isDRTTS = false; + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + #region 小怪 + [ScriptMethod(name: "Sweeping Gouge", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40668"])] + public void SweepingGouge(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"SweepingGouge-{@event.SourceId()}"; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(9); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Sweeping Gouge Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:40668"], userControl: false)] + public void SweepingGougeClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"SweepingGouge-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Thunderball", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40666"])] + public void Thunderball(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"Thunderball-{@event.SourceId()}"; + //dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(8); + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Thunderball Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:40666"], userControl: false)] + public void ThunderballClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Thunderball-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Catapult", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40672"])] + public void Catapult(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"Catapult-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 3750; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Catapult Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:40672"], userControl: false)] + public void CatapultClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Catapult-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Glass Punch", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40671"])] + public void GlassPunch(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"GlassPunch-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(7); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 3750; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Glass Punch Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:40671"], userControl: false)] + public void GlassPunchClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"GlassPunch-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Landslip", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:41118"])] + public void Landslip(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"Landslip-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(12); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Landslip Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:41118"], userControl: false)] + public void LandslipClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Landslip-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Plummet", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40676"])] + public void Plummet(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"Plummet-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Plummet Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:40676"], userControl: false)] + public void PlummetClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Plummet-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Wild Horn", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40675"])] + public void WildHorn(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"WildHorn-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(15); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Wild Horn Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:40675"], userControl: false)] + public void WildHornClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"WildHorn-{@event.SourceId()}"); + } + + #endregion + + + #region Boss1 + [ScriptMethod(name: "Electrical Overload", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40635"])] + public void ElectricalOverload(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + accessory.TTS("AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "CellShock", eventType: EventTypeEnum.EnvControl, eventCondition: ["State:regex:^(00020001|00200010)$"])] + public void CellShock(Event @event, ScriptAccessory accessory) + { + var EffectPositions = new Dictionary + { + { 0x0D, new Vector3(81.132f, -0.75f, 268.868f) }, + { 0x0E, new Vector3(81.132f, -0.75f, 285.132f) }, + { 0x0F, new Vector3(64.868f, -0.75f, 268.868f) }, + { 0x10, new Vector3(64.868f, -0.75f, 285.132f) } + }; + var EffectPositionsPairs = new Dictionary + { + { 0x0D, 0x10 }, + { 0x0E, 0x0F }, + { 0x0F, 0x0E }, + { 0x10, 0x0D } + }; + + var index = @event.Index(); + var state = @event.State(); + + if (state == 0x00200010 && EffectPositionsPairs.TryGetValue(index, out var remappedIndex)) + index = remappedIndex; + + if (EffectPositions.TryGetValue(index, out var position)) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "CellShock"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.ScaleMode = ScaleMode.ByTime; + dp.Scale = new Vector2(26); + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "Lightning Storm", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40637"])] + public async void LightningStorm(Event @event, ScriptAccessory accessory) + { + await Task.Delay(50); + if (LightningStormTTSCount == 0 || LightningStormTTSCount == 4 || LightningStormTTSCount == 7 || LightningStormTTSCount == 12) + { + if (isText) accessory.Method.TextInfo("全体分散", duration: 4700, true); + accessory.TTS("全体分散", isTTS, isDRTTS); + } + LightningStormTTSCount++; + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Lightning Storm"; + dp.Color = new Vector4(138 / 255.0f, 43 / 255.0f, 251 / 226.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Raw Electrope", eventType: EventTypeEnum.Targetable, eventCondition: ["SourceId:40002641"])] + public void RawElectrope(Event @event, ScriptAccessory accessory) + { + if (RawElectropeCount == 0) + { + if (isText) accessory.Method.TextInfo("优先攻击小怪", duration: 4700, true); + accessory.TTS("优先攻击小怪", isTTS, isDRTTS); + RawElectropeCount++; + } + } + + + [ScriptMethod(name: "Sparking Fissure", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:41258"])] + public async void SparkingFissure(Event @event, ScriptAccessory accessory) + { + await Task.Delay(8700); + if (isText) accessory.Method.TextInfo("AOE", duration: 3700, true); + accessory.TTS("AOE", isTTS, isDRTTS); + } + + + [ScriptMethod(name: "Clear", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:17998"])] + public void Boss1Clear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + #endregion + + + #region Boss2 + [ScriptMethod(name: "Boss2死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40658"])] + public void Boss2Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4700, true); + accessory.TTS("死刑准备", isTTS, isDRTTS); + } + + [ScriptMethod(name: "Phantom Flood", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40644"])] + public void PhantomFlood(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("去Boss脚下", duration: 4700, true); + accessory.TTS("去Boss脚下", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Phantom Flood"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Dark II", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4065[67])$"])] + public void DarkTwo(Event @event, ScriptAccessory accessory) + { + var isFirstAction = @event.ActionId() == 40656; + var duration = @event.DurationMilliseconds(); + var delay = isFirstAction ? 0 : 4700; + var destroyAt = isFirstAction ? 4700 : 3800; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Phantom Flood"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.TargetId(); + dp.Delay = delay; + dp.Scale = DarkTwoCount < 12 ? new Vector2(5) : new Vector2(35); + dp.Radian = float.Pi / 180 * 30; + dp.Rotation = float.Pi / 180 * @event.SourceRotation() + float.Pi / 180 * 30; + dp.DestoryAt = destroyAt; + + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + + DarkTwoCount++; + } + + + [ScriptMethod(name: "Telltale Tears", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40649"])] + public void TelltaleTears(Event @event, ScriptAccessory accessory) + { + if (TelltaleTearsTTSCount == 0) + { + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4700, true); + accessory.TTS("分散, 不要重叠", isTTS, isDRTTS); + + TelltaleTearsTTSCount++; + } + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Telltale Tears"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Necrohazard", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40646"])] + public void Necrohazard(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离场中, 注意目压", duration: 4700, true); + accessory.TTS("远离场中, 注意目压", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Telltale Tears"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = new Vector3(115.94f, 12.50f, -66.04f); + dp.Scale = new Vector2(18); + dp.DestoryAt = 14700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Bloodburst", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40647"])] + public async void Bloodburst(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + accessory.TTS("AOE", isTTS, isDRTTS); + } + + [ScriptMethod(name: "Soul Douse", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40651"])] + public void SoulDouse(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 4700, true); + accessory.TTS($"与{tname}分摊", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Telltale Tears"; + dp.Color = new Vector4(0 / 255.0f, 255 / 255.0f, 255 / 255.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + + + #endregion + + + #region Boss3 + [ScriptMethod(name: "Raging Claw", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40613"])] + public void RagingClaw(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("去Boss身后", duration: 4700, true); + accessory.TTS($"去Boss身后", isTTS, isDRTTS); + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Raging Claw"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(45); + dp.Radian = float.Pi / 180 * 180; + dp.DestoryAt = 5400; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + + if (RagingClawCount != 0) + { + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "Raging Claw Navi"; + dp1.Owner = accessory.Data.Me; + dp1.Color = accessory.Data.DefaultSafeColor; + dp1.ScaleMode |= ScaleMode.YByDistance; + dp1.TargetPosition = @event.TargetPosition(); + dp1.Scale = new Vector2(2f); + dp1.DestoryAt = 4500; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + RagingClawCount++; + } + + [ScriptMethod(name: "Boulder Dance", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4060[78])$"])] + public void BoulderDance(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Boulder Dance"; + dp.Color = accessory.Data.DefaultDangerColor; + //dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(7); + dp.DestoryAt = 7300; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + + //[ScriptMethod(name: "LeapingEarth", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40662"])] + //public void LeapingEarth(Event @event, ScriptAccessory accessory) + //{ + // List LeapingEarthCheck0 = new List { 0x400029F4, 0x400029F5, 0x400029F6, 0x400029F7 }; + // List LeapingEarthCheck1 = new List { 0x400029F0, 0x400029F1, 0x400029F2, 0x400029F3 }; + // lock (LeapingEarthLock) + // { + // if (LeapingEarthTargetList.Count == 4) + // { + // if (LeapingEarthTargetList.All(value => LeapingEarthCheck0.Contains(value))) + // { + // LeapingEarthResult = 0; + // } + // if (LeapingEarthTargetList.All(value => LeapingEarthCheck1.Contains(value))) + // { + // LeapingEarthResult = 1; + // } + + // if (LeapingEarthResult == 0) + // { + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"{LeapingEarthResult}LeapingEarth"; + // dp.Owner = accessory.Data.Me; + // dp.Color = accessory.Data.DefaultSafeColor; + // dp.ScaleMode |= ScaleMode.YByDistance; + // dp.TargetPosition = new Vector3(41.77f, -87.90f, -707.98f); + // dp.Scale = new(2); + // dp.DestoryAt = 4000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + // } + // if (LeapingEarthResult == 1) + // { + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"{LeapingEarthResult}LeapingEarth"; + // dp.Owner = accessory.Data.Me; + // dp.Color = accessory.Data.DefaultSafeColor; + // dp.ScaleMode |= ScaleMode.YByDistance; + // dp.TargetPosition = new Vector3(34.28f, -87.90f, -716.22f); + // dp.Scale = new(2); + // dp.DestoryAt = 4000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + // } + // LeapingEarthTargetList.Clear(); + // } + + // LeapingEarthTargetList.Add(@event.TargetId()); + // } + //} + //[ScriptMethod(name: "LeapingEarth", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40662"])] + //public void LeapingEarth(Event @event, ScriptAccessory accessory) + //{ + // lock (LeapingEarthLock) + // { + + // if (LeapingEarthResult == 0) + // { + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"{LeapingEarthResult}LeapingEarth"; + // dp.Owner = accessory.Data.Me; + // dp.Color = accessory.Data.DefaultSafeColor; + // dp.ScaleMode |= ScaleMode.YByDistance; + // dp.TargetPosition = new Vector3(41.77f, -87.90f, -707.98f); + // dp.Scale = new(2); + // dp.DestoryAt = 4000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + // } + // if (LeapingEarthResult == 4) + // { + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"{LeapingEarthResult}LeapingEarth"; + // dp.Owner = accessory.Data.Me; + // dp.Color = accessory.Data.DefaultSafeColor; + // dp.ScaleMode |= ScaleMode.YByDistance; + // dp.TargetPosition = new Vector3(34.28f, -87.90f, -716.22f); + // dp.Scale = new(2); + // dp.DestoryAt = 4000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + // } + // LeapingEarthResult++; + // } + //} + + [ScriptMethod(name: "Jagged Edge", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40615"])] + public void JaggedEdge(Event @event, ScriptAccessory accessory) + { + lock (JaggedEdgeLock) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4700, true); + accessory.TTS("分散, 不要重叠", isTTS, isDRTTS); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Jagged Edge"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "Crater Carve", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40605"])] + public void CraterCarve(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Crater Carve"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(11); + dp.DestoryAt = 9200; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Leaping Earth", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40606"])] + public void LeapingEarth(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Leaping Earth"; + dp.Color = new Vector4(1, 1, 0, 1); + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 1200; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Beastly Roar", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40610"])] + public void BeastlyRoar(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离Boss", duration: 7000, true); + accessory.TTS("远离Boss", isTTS, isDRTTS); + + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Beastly Roar"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(25); + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Turali Stone IV", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40616"])] + public void TuraliStoneIV(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 4700, true); + accessory.TTS($"与{tname}分摊", isTTS, isDRTTS); + + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = "Turali Stone IV"; + dp.Color = new Vector4(0 / 255.0f, 255 / 255.0f, 255 / 255.0f, 1.0f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40618|40603)$"])] + public void BOSS3AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + accessory.TTS($"AOE", isTTS, isDRTTS); + + } + + [ScriptMethod(name: "Boss3死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40619"])] + public void Boss3Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4700, true); + accessory.TTS($"死刑准备", isTTS, isDRTTS); + } + + [ScriptMethod(name: "岩石冲击-画图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40611"])] + public void RockBlast(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Rock Blast"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(5f); + dp.DestoryAt = 700; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "岩石冲击-引导", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40611"])] + public void RockBlastNavi(Event @event, ScriptAccessory accessory) + { + Task.Delay(50); + DebugMsg($"In Navi", accessory); + DebugMsg($"Rotation: {@event.SourceRotation()}", accessory); + DebugMsg($"RockBlastNaviCount: {RockBlastNaviCount}", accessory); + if (RockBlastNaviCount == 0) + { + WaitToRemove(accessory); + DebugMsg($"RockBlastNavi: IN", accessory); + switch (@event.SourceRotation()) + { + // North ClockWise + case 1.57f: + DebugMsg($"In 1.57",accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Rock BlastNavi rect right Half 1.57"; + dp.Color = new Vector4(1, 1, 0, 1); + dp.Position = new Vector3(33.52f, -87.90f, -709.22f); + dp.Radian = float.Pi; + dp.Rotation = float.Pi / 2; + dp.Scale = new Vector2(40f); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp); + var Npos1 = new Vector3(27.94f, -87.90f, -698.07f); + var NTpos1 = new Vector3(20.98f, -87.90f, -710.16f); + DrawDisplacement(accessory, Npos1, NTpos1, 14f, 10000, "1.57 Arrow1"); + + var Npos2 = new Vector3(20.98f, -87.90f, -710.16f); + var NTpos2 = new Vector3(28.00f, -87.90f, -721.33f); + DrawDisplacement(accessory, Npos2, NTpos2, 14f, 10000, "1.57 Arrow2"); + + var Npos3 = new Vector3(28.47f, -87.90f, -721.85f); + var NTpos3 = new Vector3(39.54f, -87.90f, -721.92f); + DrawDisplacement(accessory, Npos3, NTpos3, 11f, 10000, "1.57 Arrow3"); + + return; + + // North AntiClockWise + case -1.57f: + DebugMsg($"In M-1.57", accessory); + DebugMsg($"Rotation: {@event.SourceRotation()}", accessory); + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = "Rock BlastNavi rect right Half -1.57"; + dp1.Color = new Vector4(1, 1, 0, 1); + dp1.Position = new Vector3(33.52f, -87.90f, -709.22f); + dp1.Radian = float.Pi; + dp1.Rotation = -float.Pi / 2; + dp1.Scale = new Vector2(40f); + dp1.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp1); + + var Npos1_1 = new Vector3(37.41f, -87.90f, -697.52f); + var NTpos1_1 = new Vector3(50.98f, -87.90f, -710.16f); + DrawDisplacement(accessory, Npos1_1, NTpos1_1, 14f, 10000, "-1.57 Arrow1"); + + var Npos2_2 = new Vector3(47.62f, -87.90f, -706.97f); + var NTpos2_2 = new Vector3(42.00f, -87.90f, -721.33f); + DrawDisplacement(accessory, Npos2_2, NTpos2_2, 14f, 10000, "-1.57 Arrow2"); + + var Npos3_3 = new Vector3(42.51f, -87.90f, -719.83f); + var NTpos3_3 = new Vector3(29.74f, -87.90f, -722.10f); + DrawDisplacement(accessory, Npos3_3, NTpos3_3, 11f, 10000, "-1.57 Arrow3"); + + return; + + // West and East ClockWise + case 3.14f: + var Spos = @event.SourcePosition(); + if (Spos.X >= 45 && Spos.Z <= -700 && Spos.Z >= -720) + { + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = "Rock BlastNavi rect right Half 3.14"; + dp2.Color = new Vector4(1, 1, 0, 1); + dp2.Position = new Vector3(33.52f, -87.90f, -709.22f); + dp2.Radian = float.Pi; + dp2.Rotation = float.Pi; + dp2.Scale = new Vector2(40f); + dp2.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp2); + + var Epos1 = new Vector3(22.58f, -87.90f, -702.92f); + var ETpos1 = new Vector3(33.42f, -87.90f, -696.70f); + DrawDisplacement(accessory, Epos1, ETpos1, 13f, 10000, "3.14E Arrow1"); + + var Epos2 = new Vector3(33.83f, -87.90f, -696.47f); + var ETpos2 = new Vector3(45.85f, -87.90f, -703.79f); + DrawDisplacement(accessory, Epos2, ETpos2, 14f, 10000, "3.14E Arrow2"); + + var Epos3 = new Vector3(45.71f, -87.90f, -703.78f); + var ETpos3 = new Vector3(46.45f, -87.90f, -715.07f); + DrawDisplacement(accessory, Epos3, ETpos3, 11f, 10000, "3.14E Arrow3"); + } else + { + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = "Rock BlastNavi rect right Half 3.14"; + dp2.Color = new Vector4(1, 1, 0, 1); + dp2.Position = new Vector3(33.52f, -87.90f, -709.22f); + dp2.Radian = float.Pi; + dp2.Rotation = float.Pi; + dp2.Scale = new Vector2(40f); + dp2.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp2); + + var Wpos1 = new Vector3(45.78f, -87.90f, -704.25f); + var WTpos1 = new Vector3(33.73f, -87.90f, -695.72f); + DrawDisplacement(accessory, Wpos1, WTpos1, 14f, 10000, "3.14W Arrow1"); + + var Wpos2 = new Vector3(34.41f, -87.90f, -696.13f); + var WTpos2 = new Vector3(21.66f, -87.90f, -703.56f); + DrawDisplacement(accessory, Wpos2, WTpos2, 14f, 10000, "3.14W Arrow2"); + + var Wpos3 = new Vector3(22.33f, -87.90f, -703.18f); + var WTpos3 = new Vector3(21.61f, -87.90f, -714.67f); + DrawDisplacement(accessory, Wpos3, WTpos3, 11f, 10000, "3.14W Arrow3"); + } + + return; + + // West and East AntiClockWise + case -0f: + var Spos0 = @event.SourcePosition(); + if (Spos0.X >= 45 && Spos0.Z <= -700 && Spos0.Z >= -720) + { + var dp4 = accessory.Data.GetDefaultDrawProperties(); + dp4.Name = "Rock BlastNavi rect right Half 3.14"; + dp4.Color = new Vector4(1, 1, 0, 1); + dp4.Position = new Vector3(33.52f, -87.90f, -709.22f); + dp4.Radian = float.Pi; + dp4.Rotation = float.Pi / 180; + dp4.Scale = new Vector2(40f); + dp4.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp4); + + var Epos1_1 = new Vector3(22.79f, -87.90f, -717.67f); + var ETpos1_1 = new Vector3(34.76f, -87.90f, -723.53f); + DrawDisplacement(accessory, Epos1_1, ETpos1_1, 14f, 10000, "-0E Arrow1"); + + var Epos2_2 = new Vector3(35.25f, -87.90f, -723.81f); + var ETpos2_2 = new Vector3(46.42f, -87.90f, -715.65f); + DrawDisplacement(accessory, Epos2_2, ETpos2_2, 13f, 10000, "-0E Arrow2"); + + var Epos3_3 = new Vector3(45.62f, -87.90f, -716.17f); + var ETpos3_3 = new Vector3(46.61f, -87.90f, -706.54f); + DrawDisplacement(accessory, Epos3_3, ETpos3_3, 11f, 10000, "-0E Arrow3"); + } else + { + var dp3 = accessory.Data.GetDefaultDrawProperties(); + dp3.Name = "Rock BlastNavi rect right Half 3.14"; + dp3.Color = new Vector4(1, 1, 0, 1); + dp3.Position = new Vector3(33.52f, -87.90f, -709.22f); + dp3.Radian = float.Pi; + dp3.Rotation = float.Pi / 180; + dp3.Scale = new Vector2(40f); + dp3.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp3); + + var Wpos1_1 = new Vector3(46.51f, -87.90f, -714.42f); + var WTpos1_1 = new Vector3(35.83f, -87.90f, -724.43f); + DrawDisplacement(accessory, Wpos1_1, WTpos1_1, 14f, 10000, "-0W Arrow1"); + + var Wpos2_2 = new Vector3(36.30f, -87.90f, -724.04f); + var WTpos2_2 = new Vector3(22.96f, -87.90f, -717.39f); + DrawDisplacement(accessory, Wpos2_2, WTpos2_2, 15f, 10000, "-0W Arrow2"); + + var Wpos3_3 = new Vector3(22.86f, -87.90f, -717.37f); + var WTpos3_3 = new Vector3(21.23f, -87.90f, -706.96f); + DrawDisplacement(accessory, Wpos3_3, WTpos3_3, 11f, 10000, "-0W Arrow3"); + } + return; + + } + } + RockBlastNaviCount++; + } + + + private void DrawDisplacement(ScriptAccessory accessory, Vector3 pos, Vector3 target, float scale, int duration, string name, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + //dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + //dp.ScaleMode |= ScaleMode.YByDistance; + dp.Position = pos; + dp.TargetPosition = target; + dp.Scale = new Vector2(2f, scale); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private async void WaitToRemove(ScriptAccessory accessory) + { + await Task.Delay(20000); + RockBlastNaviCount = 0; + DebugMsg("Successfully RockBlastNaviCount = 0", accessory); + } + + #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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + + +public static class Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isTTS, bool isDRTTS) + { + if (isTTS) + { + accessory.Method.TTS(text); + } + } +} + +public static class IbcHelper +{ + public static IBattleChara? GetById(ScriptAccessory accessory, uint id) + { + return (IBattleChara?)accessory.Data.Objects.SearchByEntityId(id); + } + + public static IBattleChara? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me) as IBattleChara; + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } +} + +public static unsafe class IBattleCharaExtensions +{ + public static bool HasStatus(this IBattleChara ibc, uint statusId, float remaining = -1) + { + return ibc.StatusList.Any(s => s.StatusId == statusId && s.RemainingTime > remaining); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } + +} diff --git a/VeeverSW/07-DawnTrail/the-Clyteum.cs b/VeeverSW/07-DawnTrail/the-Clyteum.cs new file mode 100644 index 0000000..8707772 --- /dev/null +++ b/VeeverSW/07-DawnTrail/the-Clyteum.cs @@ -0,0 +1,2181 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Common.Lua; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Data; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Net.Http; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Xml.Linq; +using static KodakkuAssist.Script.ScriptAccessory; +using KodaMarkType = KodakkuAssist.Module.GameOperate.MarkType; + +namespace Veever.DawnTrail.the_Clyteum; + +[ScriptType(name: Name, territorys: [1345], guid: "5f365ada-2833-4164-92b3-8d11b7275b80", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateStr)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class the_Clyteum +{ + const string NoteStr = + $""" + v{Version} + ----- 请在使用前阅读注意事项 以及根据情况修改用户设置 ----- + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. 小怪部分和一些细节部分后续版本更新 + 鸭门 + ---------------------------------- + ----- Please read the notes before use and adjust user settings as needed. ----- + 1. If you need a draw or notice any issues, @ me on DC or DM me. + 2. English version will be added in the future update, I'll update it when XIV strings comp is updated. + 3. Mobs and some details will be added in future updates. + Duckmen. + """; + + const string UpdateStr = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.100 军工要地克吕提俄斯魔导工厂 [the Clyteum]"; + private const string Version = "0.0.0.6"; + private const string DebugVersion = "a"; + + private const bool Debugging = false; + + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + [UserSetting("是否自动使用防击退(Auto anti-knockback)")] + public bool useAntiKnockBack { get; set; } = false; + + //[UserSetting("指路开关(Guide arrow toggle)")] + //public bool isLead { get; set; } = true; + + //[UserSetting("目标标记开关(Target Marker toggle)")] + //public bool isMark { get; set; } = true; + + //[UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + //public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + + private readonly object CountLock = new object(); + private bool isEdgeTTS = false; + + + public void DebugMsg(string str, ScriptAccessory sa) + { + if (!isDebug) return; + sa.Log.Debug($"[DEBUG] {str}"); + } + + private ScriptAccessory _sa = null; + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + _sa = sa; + + _ = ScriptVersionChecker.CheckVersionAsync( + sa, + "5f365ada-2833-4164-92b3-8d11b7275b80", + Version, + showNotification: true + ); + + + sa.Method.ClearFrameworkUpdateAction(this); + + RefreshParams(); + } + + private void RefreshParams() + { + + } + + #region Mobs + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void destoryCancelAction(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($".*{ev.TargetId}"); + } + + [ScriptMethod(name: "坚实岩牢 - Rock Throw", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48962"])] + public void RockThrow(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(5.9f), 3700, $"Rock Throw-{ev.SourceId}", color: sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Rock Throw Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:48962"], userControl: false)] + public void RockThrowClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Rock Throw-{ev.SourceId}"); + } + + [ScriptMethod(name: "毒波 - Mow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:49184"])] + public void Mow(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(7f), 120, 3700, $"Mow-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Mow Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:49184"], userControl: false)] + public void MowClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Mow-{ev.SourceId}"); + } + #endregion + + + #region Boss1 + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "凶眼注目 - Eyes on Me", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48896"])] + public void Boss1AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + + [ScriptMethod(name: "石化光束 - Petrifying Beam", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^5017[78]$"])] + public void Boss1Beam(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(70f), 100, 8500, $"Petrifying Beam-{ev.SourceId}", new Vector4(0, 1, 1, 3), scaleByTime: false); + } + + [ScriptMethod(name: "质量导弹 - Penetrator Missile", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48901"])] + public void Boss1Stack(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 4700, $"Penetrator Missile-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + + [ScriptMethod(name: "对地导弹 - Surface Missile", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48898"])] + public void SurfaceMissile(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(5f), 2700, $"Surface Missile-{ev.SourceId}", color: new Vector4(1, 0, 0, ColorAlpha)); + } + + [ScriptMethod(name: "动态扫描仪 - Motion Scanner", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48893"])] + public void MotionScanner(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "即将被扫描时不要动" : "Don't move when about to be scanned"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 5000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + #endregion + + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "暗黑冲击 - Ripples of Gloom", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48884"])] + public void Boss2AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + + + [ScriptMethod(name: "肉弹 - Mortifying Flesh", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48871$"])] + public void Boss2MortifyingFlesh(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(16, 40f), 4700, $"Mortifying Flesh-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "肉弹2 - Mortifying Flesh2", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^50400|48876$"])] + public void Boss2MortifyingFlesh2(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(16, 40f), 2500, $"Mortifying Flesh2-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "肉压杀 - Bodyweight Exorcism", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48878$"])] + public void Boss2BodyweightExorcism(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "注意击退" : "Be care of Knockback"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2800, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "Bodyweight Exorcism"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetPosition = ev.SourcePosition; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, 8); + dp.DestoryAt = 2800; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + if (useAntiKnockBack) sa.Method.antiKnockBack(sa); + } + + [ScriptMethod(name: "呕吐 - Basic Vomit", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^50361$"])] + public void Boss2BasicVomit(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(50f), 120, 4700, $"Basic Vomit-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "黑暗喷流 - Evil Emission", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48885$"])] + public void Boss2EvilEmission(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分散" : "Spread"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "黑暗重爆 - Profane Pressure", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48887"])] + public void Boss2ProfanePressure(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 4700, $"Profane Pressure-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "废品处理 - Rubbish Disposal", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48920"])] + public void Boss3AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "虚无黑暗 - Void Dark", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^50313$"])] + public void Boss3VoidDark(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(70f), 180, 4700, $"Void Dark-{ev.SourceId}", sa.Data.DefaultDangerColor, scaleByTime: false); + } + + [ScriptMethod(name: "废料瘴气 - Metallic Miasma", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(48937|48943)$"])] + public void Boss3MetallicMiasma(Event ev, ScriptAccessory sa) + { + int duration = int.TryParse(ev["DurationMilliseconds"]?.ToString(), out var d) ? d : 4700; + + DrawHelper.DrawFanObject(sa, ev.SourceId, 0, new Vector2(60f), 30, duration, $"Metallic Miasma-{ev.SourceId}-{duration}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + [ScriptMethod(name: "废料光环 - Cast-off Halo", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(48935|48940)$"])] + public void Boss3CastOffHalo(Event ev, ScriptAccessory sa) + { + int duration = int.TryParse(ev["DurationMilliseconds"]?.ToString(), out var d) ? d : 6500; + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(7f), duration, $"Cast-off Halo-{ev.SourceId}-{duration}", color: new Vector4(1, 0, 0, ColorAlpha), scaleByTime: false); + } + + [ScriptMethod(name: "贪食之线 - Gluttonous Wire", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:48930"])] + public void Boss3GluttonousWire(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawCircleObject(sa, ev.TargetId, new Vector2(6f), 4700, $"Gluttonous Wire-{ev.SourceId}", color: sa.Data.DefaultSafeColor, scaleByTime: false); + } + + [ScriptMethod(name: "影戏 - Shadow Play", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:50314"])] + public void Boss3TankBuster(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "范围死刑,远离人群" : "AOE tankbuster — Stay away from party"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + else + { + string msg = language == Language.Chinese ? "远离范围死刑" : "Avoid AOE tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + [ScriptMethod(name: "愤怒之线 - Wrathful Wire", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48928$"])] + public void Boss3WrathfulWire(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分散" : "Spread"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + } + + #endregion +} + + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return ParseHexId(ev["Index"], out var index) ? index : 0; + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + + +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(); + } + + /// + /// 获取玩家的职能 + /// Return: "Tank"(坦克) / "Healer"(治疗) / "Melee DPS"(近战物理) / "Ranged Physical DPS"(远程物理) / "Ranged Magical DPS"(远程魔法) / "Unknown" / "None" + /// 大版本更新需要维护 + /// + public static string GetPlayerRole(this ScriptAccessory sa, IPlayerCharacter? playerObject) + { + if (playerObject == null) return "None"; + + return playerObject.ClassJob.RowId switch + { + // Tank: 骑士、战士、暗黑骑士、绝枪战士 + 19 or 21 or 32 or 37 => "Tank", + // Healer: 白魔法师、学者、占星术士、贤者 + 24 or 28 or 33 or 40 => "Healer", + // Melee DPS: 武僧、龙骑士、忍者、武士、钐镰客、蝰蛇剑士 + 20 or 22 or 30 or 34 or 39 or 41 => "Melee DPS", + // Ranged Physical DPS: 吟游诗人、机工士、舞者 + 23 or 31 or 38 => "Ranged Physical DPS", + // Ranged Magical DPS: 黑魔法师、召唤师、赤魔法师、青魔法师、绘灵法师 + 25 or 27 or 35 or 36 or 42 => "Ranged Magical DPS", + _ => "Unknown" + }; + } + + 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; + } + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + + public static string GetObjectName(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return "None"; + return obj.Name.ToString(); + } + + /// + /// 获取指定标记索引的对象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 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 ActionExt +{ + public static unsafe bool IsReadyWithCanCast(uint actionId, ActionType actionType) + { + var am = ActionManager.Instance(); + if (am == null) return false; + + var adjustedId = am->GetAdjustedActionId(actionId); + + // 0 = Ready) + if (am->GetActionStatus(actionType, adjustedId) != 0) + return false; + + ulong targetId = 0; + var ts = TargetSystem.Instance(); + if (ts != null && ts->GetTargetObject() != null) + targetId = ts->GetTargetObject()->GetGameObjectId(); + + return am->GetActionStatus(actionType, adjustedId, targetId) == 0; + } + + public static bool IsSpellReady(this uint spellId) => IsReadyWithCanCast(spellId, ActionType.Action); + public static bool IsAbilityReady(this uint abilityId) => IsReadyWithCanCast(abilityId, ActionType.EventAction); +} + +#region 计算函数 + +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; + + /// + /// 将弧度值规范化到 -π 到 π 范围 + /// + public static float NormalizeRadian(this float rad) + { + rad = (rad + 2 * float.Pi) % (2 * float.Pi); // 先转到 0-2π + if (rad > float.Pi) rad -= 2 * float.Pi; // 如果大于 π,转到负数范围 + return rad; + } + + /// + /// 获得任意点与中心点的弧度值,以(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 static Vector3 GetPositionByAngle(Vector3 origin, float angleInDegrees, float distance) + { + float radian = angleInDegrees * MathF.PI / 180f; + + return new Vector3( + origin.X + distance * MathF.Cos(radian), + origin.Y, + origin.Z + distance * MathF.Sin(radian) + ); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, KodaMarkType.Attack1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack4, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack5, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack6, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack7, true); + sa.Method.Mark(0xE000000, KodaMarkType.Attack8, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Bind3, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop1, true); + sa.Method.Mark(0xE000000, KodaMarkType.Stop2, true); + sa.Method.Mark(0xE000000, KodaMarkType.Square, true); + sa.Method.Mark(0xE000000, KodaMarkType.Circle, true); + sa.Method.Mark(0xE000000, KodaMarkType.Cross, true); + sa.Method.Mark(0xE000000, KodaMarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, KodaMarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, KodaMarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static unsafe void SetModelScale(ScriptAccessory sa, uint dataId, float scale, float VfxScale) + { + sa.Method.RunOnMainThreadAsync(() => + { + var obj = sa.Data.Objects.FirstOrDefault(o => o.DataId == dataId); + if (obj == null) return; + + var gameObj = (GameObject*)obj.Address; + if (gameObj == null || !gameObj->IsReadyToDraw()) return; + + gameObj->Scale = scale; + gameObj->VfxScale = VfxScale; + + if (gameObj->IsCharacter()) + { + var chara = (BattleChara*)gameObj; + chara->Character.CharacterData.ModelScale = scale; + } + + gameObj->DisableDraw(); + gameObj->EnableDraw(); + }); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } + + [Flags] + public enum DrawState : uint + { + Invisibility = 0x00_00_00_02, + IsLoading = 0x00_00_08_00, + SomeNpcFlag = 0x00_00_01_00, + MaybeCulled = 0x00_00_04_00, + MaybeHiddenMinion = 0x00_00_80_00, + MaybeHiddenSummon = 0x00_80_00_00, + } + + public static unsafe DrawState* ActorDrawState(IGameObject actor) + => (DrawState*)(&((GameObject*)actor.Address)->RenderFlags); + + /// + /// 检查对象可见性(Read) + /// + /// ScriptAccessory + /// Obj need check + /// true=检查可见,false=检查不可见 + /// 如果符合检查条件返回 True + public static unsafe bool IsActorVisible(this ScriptAccessory sa, IGameObject? obj, bool checkVisible = true) + { + if (obj == null) return false; + + try + { + var state = *ActorDrawState(obj); + bool isVisible = (state & DrawState.Invisibility) == 0; + + return checkVisible ? isVisible : !isVisible; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } + + /// + /// 设置对象可见性(Write) + /// + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + if (actor == null) return; + + try + { + var statePtr = ActorDrawState(actor); + if (visible) + *statePtr &= ~DrawState.Invisibility; + else + *statePtr |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + throw; + } + } + + /// + /// Check DrawState + /// + public static unsafe bool HasDrawState(this ScriptAccessory sa, IGameObject? actor, DrawState state) + { + if (actor == null) return false; + + try + { + return (*ActorDrawState(actor) & state) != 0; + } + catch (Exception e) + { + sa.Log.Error($" {e} "); + return false; + } + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float? rotation = null, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + if (rotation.HasValue) dp.Rotation = rotation.Value; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTargetWithRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosNoTarget(ScriptAccessory accessory, Vector3 pos, Vector2 scale, float rotation, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Scale = scale; + dp.Delay = delay; + dp.Rotation = rotation; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectPosTarget(ScriptAccessory accessory, Vector3 pos, Vector3 targetpos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None, Vector3? offset = null, DrawModeEnum drawMode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.TargetPosition = targetpos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawMode, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTargetPos(ScriptAccessory accessory, ulong owner, Vector3 targetPos, Vector2 scale, int duration, string name, + Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, + int duration, string name, Vector4? color = null, int delay = 0, + bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default, bool scaleByTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanNoRot(ScriptAccessory accessory, Vector3 position, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObjectNoRot(ScriptAccessory accessory, ulong owner, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanObject(ScriptAccessory accessory, ulong owner, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, + Vector2 scale, float angle, int duration, string name, Vector4? color = null, + int delay = 0, bool scaleByTime = true, bool fix = false, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + dp.Offset = offset ?? new Vector3(0, 0, 0); + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDount(ScriptAccessory accessory, Vector3 position, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountPos(ScriptAccessory accessory, Vector3 position, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObjectPos(ScriptAccessory accessory, ulong obj, Vector3 targetPosition, float rotation, Vector2 scale, float radian, Vector2 innerscale, + int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, Vector3? offset = null, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = obj; + dp.TargetPosition = targetPosition; + dp.Rotation = rotation; + dp.Radian = radian; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + dp.Offset = offset ?? new Vector3(0, 0, 0); + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } + + public static void DrawDountObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, Vector2 innerscale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Radian = 2 * float.Pi; + dp.Scale = scale; + dp.InnerScale = innerscale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Donut, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class Extensions +{ + public static void TTS(this ScriptAccessory accessory, string text, bool isEdgeTTS) + { + accessory.Method.TTS(text); + } + + public static void antiKnockBack(this MethodAccessory method, ScriptAccessory sa) + { + // 7559 沉稳咏唱 + // 7548 亲疏自行 + var myobj = sa.Data.MyObject; + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Tank" || IbcHelper.GetPlayerRole(sa, myobj) == "Melee DPS" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Physical DPS") + { + method.UseAction(myobj.EntityId, 7548); + } + + if (IbcHelper.GetPlayerRole(sa, myobj) == "Healer" || IbcHelper.GetPlayerRole(sa, myobj) == "Ranged Magical DPS") + { + method.UseAction(myobj.EntityId, 7559); + } + } +} + +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } + + /// + /// 获取玩家的职业名称或简称 + /// + 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 bool IsTank(IPlayerCharacter? playerObject) + { + if (playerObject == null) return false; + return playerObject.ClassJob.Value.Role == 1; + } +} + +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 + +#region 脚本版本检查 +public static class ScriptVersionChecker +{ + private const string OnlineRepoUrl = "https://raw.githubusercontent.com/VeeverSW/Kodakku-Script/refs/heads/main/OnlineRepo.json"; + private static readonly HttpClient _httpClient = new HttpClient(); + + /// + /// 在线仓库脚本信息 + /// + public class OnlineScriptInfo + { + public string Name { get; set; } = ""; + public string Guid { get; set; } = ""; + public string Version { get; set; } = ""; + public string Author { get; set; } = ""; + public string Repo { get; set; } = ""; + public string DownloadUrl { get; set; } = ""; + public string Note { get; set; } = ""; + public string UpdateInfo { get; set; } = ""; + public int[] TerritoryIds { get; set; } = Array.Empty(); + } + + /// + /// 版本 + /// + public enum VersionCompareResult + { + /// 当前版本较新或相同 + UpToDate, + /// 有新版本可用 + UpdateAvailable, + /// 未找到匹配的脚本 + NotFound, + /// 检查失败 + Error + } + + /// + /// 检查脚本版本 + /// + /// ScriptAccessory + /// 脚本GUID + /// 当前版本号 + /// 是否显示通知 + /// 版本比较结果 + public static async Task<(VersionCompareResult result, OnlineScriptInfo? onlineInfo)> CheckVersionAsync( + ScriptAccessory sa, + string guid, + string currentVersion, + bool showNotification = true) + { + try + { + sa.Log.Debug($"开始检查脚本版本 (GUID: {guid}, 当前版本: {currentVersion})"); + + var response = await _httpClient.GetStringAsync(OnlineRepoUrl); + var onlineScripts = JsonConvert.DeserializeObject>(response); + + if (onlineScripts == null || onlineScripts.Count == 0) + { + sa.Log.Error("无法解析在线仓库数据"); + return (VersionCompareResult.Error, null); + } + + var onlineScript = onlineScripts.FirstOrDefault(s => + s.Guid.Equals(guid, StringComparison.OrdinalIgnoreCase)); + + if (onlineScript == null) + { + //sa.Log.Debug($"在线仓库中未找到 GUID 为 {guid} 的脚本"); + //if (showNotification) + //{ + // sa.Method.TextInfo("该脚本未在在线仓库中注册", 3000); + //} + return (VersionCompareResult.NotFound, null); + } + + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 找到在线脚本: {onlineScript.Name}, 在线版本: {onlineScript.Version}"); + + var compareResult = CompareVersions(currentVersion, onlineScript.Version); + + if (compareResult < 0) + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新 (当前: {currentVersion})"); + if (showNotification) + { + sa.Method.TextInfo( + $"{onlineScript.Name} by {onlineScript.Author}: 发现新版本: {onlineScript.Version} 请及时更新\n当前版本: {currentVersion}", + 5000, + true); + } + return (VersionCompareResult.UpdateAvailable, onlineScript); + } + else + { + sa.Log.Debug($"{onlineScript.Name} by {onlineScript.Author}: 当前版本已是最新 (当前: {currentVersion}, 在线: {onlineScript.Version})"); + + return (VersionCompareResult.UpToDate, onlineScript); + } + } + catch (HttpRequestException ex) + { + //sa.Log.Error($"网络请求失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败: 网络错误", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + catch (Exception ex) + { + //sa.Log.Error($"版本检查失败: {ex.Message}"); + //if (showNotification) + //{ + // sa.Method.TextInfo("版本检查失败", 3000, true); + //} + return (VersionCompareResult.Error, null); + } + } + + /// + /// 比较版本号 + /// + /// 版本1 (例如: "0.0.0.3") + /// 版本2 (例如: "0.0.0.5") + /// 负数: version1 < version2, 0: 相等, 正数: version1 > version2 + private static int CompareVersions(string version1, string version2) + { + var v1Parts = version1.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + var v2Parts = version2.Split('.').Select(p => int.TryParse(p, out var num) ? num : 0).ToArray(); + + int maxLength = Math.Max(v1Parts.Length, v2Parts.Length); + + for (int i = 0; i < maxLength; i++) + { + int v1Part = i < v1Parts.Length ? v1Parts[i] : 0; + int v2Part = i < v2Parts.Length ? v2Parts[i] : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } +} +#endregion diff --git a/VeeverSW/07-DawnTrail/the-Meso-Terminal.cs b/VeeverSW/07-DawnTrail/the-Meso-Terminal.cs new file mode 100644 index 0000000..07231fa --- /dev/null +++ b/VeeverSW/07-DawnTrail/the-Meso-Terminal.cs @@ -0,0 +1,1883 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Veever.DawnTrail.TheMesoTerminal; + +[ScriptType(name: Name, territorys: [1292], guid: "c74e8153-46e1-4a18-97bd-250463781ed2", + version: Version, author: "Veever", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + + +public class TheMesoTerminal +{ + const string NoteStr = + """ + v0.0.0.5 + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. Boss3 NPC聚集在一起的圆圈绘画如果掉线可能会导致不画 + 鸭门 + ------------------------------ + 1. If you need a draw for a mechanic or notice any issues, @ me on DC or DM me. + 2. Boss 3: The circle draw for Bombardment may fail to draw if you disconnect + Duckmen + """; + + const string UpdateInfo = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.100 永久幽界中央终端 [the Meso Terminal]"; + private const string Version = "0.0.0.5"; + private const string DebugVersion = "a"; + + private const bool Debugging = true; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static readonly Vector3 Center = new Vector3(100, 0, 100); + + // TODO 根据第一个AOE的读条调整 + private static uint BossDataId = 9708; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + //[UserSetting("是否自动使用防击退(Auto anti-knockback)")] + //public bool useaction { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + [UserSetting("目标标记开关(Target Marker toggle)")] + public bool isMark { get; set; } = true; + + [UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + public bool PostNamazuPrint { get; set; } = true; + + [UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + public int PostNamazuPort { get; set; } = 2019; + + [UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + private Dictionary _tethersDict = new(); + private static bool _initHint = false; + + private List _terrorList = new(); + private readonly object CountLock = new object(); + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + + _terrorList.Clear(); + } + + + + + private static IGameObject? GetBossObject(ScriptAccessory sa) + { + return sa.GetByDataId(BossDataId).FirstOrDefault(); + } + + #region 小怪 + [ScriptMethod(name: "---- 小怪-Mobs ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void mobs(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "电流冲击-Electric Shock", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44040"])] + public void ElectricShock(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(5f), 4000, $"ElectricShock-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "DarkMist Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:44040"], userControl: false)] + public void ElectricShockClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"ElectricShock-{ev.SourceId}"); + } + + [ScriptMethod(name: "王国重力-Alexandrian Gravity", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44041"])] + public void AlexandrianGravity(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 2700, $"Alexandrian Gravity-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Alexandrian Gravity Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:44041"], userControl: false)] + public void AlexandrianGravityClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Alexandrian Gravity-{ev.SourceId}"); + } + + [ScriptMethod(name: "振动波-Pressure Wave", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44044"])] + public void PressureWave(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawFanOwner(sa, ev.SourceId, 0, new Vector2(6f), 120, 2700, $"PressureWave-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Pressure Wave Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:44044"], userControl: false)] + public void PressureWaveClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"PressureWave-{ev.SourceId}"); + } + + [ScriptMethod(name: "飞刀乱掷 & 超压斧-Rusted Knives & Overpower", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4404[56])$"])] + public void RustedKnives_Overpower(Event ev, ScriptAccessory sa) + { + DebugMsg($"In RustedKnives_Overpower, ActionId: {ev.ActionId}", sa); + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(5f), 2700, $"Rusted Knives & Overpower-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "Rusted Knives & Overpower Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:regex:^(4404[56])$"], userControl: false)] + public void RustedKnives_OverpowerClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Rusted Knives & Overpower-{ev.SourceId}"); + } + + [ScriptMethod(name: "雷转质射线-Electray", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44048)$"])] + public void Electray(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(4, 14), 3700, $"Electray-{ev.SourceId}", scalemode: ScaleMode.ByTime); + } + + [ScriptMethod(name: "Electray Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:regex:^(44048)$"], userControl: false)] + public void ElectrayClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Electray-{ev.SourceId}"); + } + + [ScriptMethod(name: "信仰之剑-Steelforged Belief", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(45042)$"])] + public void SteelforgedBelief(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(6f), 3700, $"Steelforged Belief-{ev.SourceId}", sa.Data.DefaultDangerColor); + } + + [ScriptMethod(name: "SteelforgedBelief Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:regex:^(45042)$"], userControl: false)] + public void SteelforgedBeliefClear(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"Steelforged Belief-{ev.SourceId}"); + } + #endregion + + + #region Boss1 + [ScriptMethod(name: "---- Boss1 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss1(Event ev, ScriptAccessory sa) + { + } + [ScriptMethod(name: "广域撒药-Medicine Field", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43798"])] + public void MedicineField(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "大AOE" : "Heavy AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "爆炸药液-Pungent Aerosol", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43807"])] + public void PungentAerosol(Event ev, ScriptAccessory sa) + { + //DrawHelper.DrawDisplacementObject(sa, ev.TargetId, new Vector2(1, 24f), 5200, $"Pungent Aerosol-{ev.SourceId}", , fix: true, drawmode: DrawModeEnum.Default); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "MesmerizingMelody"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetPosition = ev.EffectPosition; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, 24f); + dp.DestoryAt = 4000; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + } + + [ScriptMethod(name: "正面喷雾-Biochemical Front", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43802"])] + public void BiochemicalFront(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "远离正面" : "Avoid front"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + DrawHelper.DrawFanOwner(sa, ev.SourceId, 0, new Vector2(40), 180, 4700, $"Biochemical Front-{ev.SourceId}"); + + } + + [ScriptMethod(name: "高压缩药弹-Concentrated Dose", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43799"])] + public void ConcentratedDose(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "Dot死刑点名" : "Dot Tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "圆状散布-Sterile Sphere", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4380[56])$"])] + public void SterileSphere(Event ev, ScriptAccessory sa) + { + // 43805 big 15f + // 43806 small 8f + DebugMsg($"In SterileSphere, ActionId: {ev.ActionId}", sa); + if (ev.ActionId == 43805) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(15f), 5200, $"Sterile Sphere-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), drawmode: DrawModeEnum.Imgui); + } + + if (ev.ActionId == 43806) + { + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(8f), 5200, $"Sterile Sphere-{ev.SourceId}", new Vector4(1, 0, 0, ColorAlpha), drawmode: DrawModeEnum.Imgui); + } + } + + + + + #endregion + + + #region Boss2 + [ScriptMethod(name: "---- Boss2 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss2(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43578"], suppress: 5000)] + public void boss2AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "Boss连线检测和标记 - Boss Thther Check & mark", eventType: EventTypeEnum.Tether, eventCondition: ["Id:00F9"])] + public void bossTetherCheck(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "攻击连线Boss" : "Attack tethered Boss"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + if (isMark) sa.Method.MarkClear(); + if (isMark) sa.Method.Mark((uint)ev.SourceId, MarkType.Attack1, LocalMark); + } + } + + [ScriptMethod(name: "斩足刃 - Dismemberment", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43587"])] + public void Dismemberment(Event ev, ScriptAccessory sa) + { + DebugMsg($"In Dismemberment, SourceId: {ev.SourceId}, TargetId: {ev.TargetId}", sa); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Dismemberment: {ev.SourceId}"; + dp.Color = new Vector4(1, 1, 0, 1); + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(4, 16); + dp.DestoryAt = 5700; + dp.ScaleMode = ScaleMode.ByTime; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "断罪的雷剑绘制 - Peal of Judgment Draw", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:43594"])] + public void PealofJudgment(Event ev, ScriptAccessory sa) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Peal of Judgment: {ev.SourceId}"; + dp.Color = new Vector4(1, 0, 1, 1); + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(4, 2); + dp.DestoryAt = 500; + dp.Offset = new Vector3(0, 0, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + // TODO + var dp1 = sa.Data.GetDefaultDrawProperties(); + dp1.Name = $"Peal of Judgment arraw: {ev.SourceId}"; + dp1.Color = sa.Data.DefaultSafeColor; + dp1.Owner = ev.SourceId; + dp1.Scale = new Vector2(1, 2); + dp1.DestoryAt = 500; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp1); + } + + + [ScriptMethod(name: "月环断 - Execution Wheel", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43596"])] + public void ExecutionWheel(Event ev, ScriptAccessory sa) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Execution Wheel: {ev.SourceId}"; + dp.Color = sa.Data.DefaultDangerColor; + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(9); + dp.InnerScale = new Vector2(4); + dp.Radian = 2 * float.Pi; + dp.DestoryAt = 3200; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + + + [ScriptMethod(name: "圆月断 - Execution Wheel", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43595"])] + public void ChoppingBlock(Event ev, ScriptAccessory sa) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Chopping Block: {ev.SourceId}"; + dp.Color = sa.Data.DefaultDangerColor; + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(6); + dp.DestoryAt = 3200; + dp.ScaleMode = ScaleMode.ByTime; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "断罪的铁球 - Flaying Flail", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43592"])] + public void FlayingFlail(Event ev, ScriptAccessory sa) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"Flaying Flail: {ev.SourceId}"; + dp.Color = sa.Data.DefaultDangerColor; + dp.Position = ev.EffectPosition; + dp.Scale = new Vector2(5); + dp.DestoryAt = 4700; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "破碎弹 - Will Breaker", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:44856"])] + public void WillBreaker(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "打断Boss" : "Interrupt!"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + } + + [ScriptMethod(name: "死刑宣告 - Death Penalty", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43588"])] + public async void DeathPenalty(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + await Task.Delay(4500); + string msg = language == Language.Chinese ? "驱散自身死宣" : "Esuna Doom"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + } + + [ScriptMethod(name: "焦热刑具标记 - Hellmaker mark", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:18642"])] + public async void Hellmakermark(Event ev, ScriptAccessory sa) + { + Vector3 pos = ev.SourcePosition; + Vector3 mypos = sa.Data.MyObject?.Position ?? Vector3.Zero; + + Vector3 testpos = new Vector3(69.15f, -490.00f, -258.32f); + + var distance = Vector3.Distance(pos, mypos); + + var testdistance = Vector3.Distance(pos, testpos); + DebugMsg($"In Hellmakermark, distance: {testdistance}", sa); + + DebugMsg($"pos: {pos}, mypos: {mypos}", sa); + DebugMsg($"In Hellmakermark, distance: {distance}", sa); + + if (distance <= 18) + { + string msg = language == Language.Chinese ? "攻击焦热刑具" : "Attack Hellmaker"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + if (isMark) sa.Method.MarkClear(); + if (isMark) sa.Method.Mark((uint)ev.SourceId, MarkType.Attack1, LocalMark); + } + + } + + + #endregion + + #region Boss3 + [ScriptMethod(name: "---- Boss3 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void Boss3(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "AOE提示 - AOE Notify", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43825"], suppress: 5000)] + public void boss3AOE(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "雷转质射线 - Electray", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43810"])] + public void Boss3Electray(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(8, 45), 4700, $"Electray-{ev.SourceId}", scalemode: ScaleMode.ByTime); + sa.Method.RemoveDraw($"Bombardment"); + } + + + [ScriptMethod(name: "小怪出现 - Terror Spawn", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:18624"])] + public void TerrorSpawn(Event ev, ScriptAccessory sa) + { + lock (CountLock) + { + if (!_terrorList.Contains(ev.SourceId)) + { + _terrorList.Add(ev.SourceId); + DebugMsg($"Terror spawned: {ev.SourceId}, total: {_terrorList.Count}", sa); + } + } + } + + + [ScriptMethod(name: "小怪消失 - Terror Despawn", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:18624"])] + public void TerrorDespawn(Event ev, ScriptAccessory sa) + { + _terrorList.Remove(ev.SourceId); + DebugMsg($"Terror despawned: {ev.SourceId}, total: {_terrorList.Count}", sa); + } + + + public static bool AlmostEqual(float a, float b, float eps) => Math.Abs(a - b) <= eps; + public static bool AlmostEqual(Vector3 a, Vector3 b, float eps) => Vector3.Distance(a, b) <= eps; + + [ScriptMethod(name: "轰炸机制 - Bombardment", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(1870[5-8])$"])] + public void Bombardment(Event ev, ScriptAccessory sa) + { + DebugMsg($"In Bombardment, terrorList count: {_terrorList.Count}", sa); + //if (BombardmentLock) return; + if (ExtensionVisibleMethod.IsCharacterVisible((ICharacter)IbcHelper.GetByDataId(sa, 18623).FirstOrDefault())) return; + DebugMsg($"No return", sa); + if (_terrorList.Count > 6) + { + var count = _terrorList.Count; + var pos = ev.SourcePosition; + var big = false; + + for (int i = 0; i < count; i++) + { + var t = IbcHelper.GetById(sa, _terrorList[i]); + if (t?.EntityId == ev.SourceId) + { + continue; + } + if (t != null && AlmostEqual(t.Position, pos, 6f)) + { + sa.Log.Debug($"tid: {t.GameObjectId}, tpos: {t.Position}, pos: {pos}, visible : {ExtensionVisibleMethod.IsCharacterVisible((ICharacter)t)}"); + if (!ExtensionVisibleMethod.IsCharacterVisible((ICharacter)t)) continue; + big = true; + sa.Log.Debug($"isbig: tid: {t.GameObjectId}, tpos: {t.Position}, pos: {pos}, visible : {ExtensionVisibleMethod.IsCharacterVisible((ICharacter)t)}"); + break; + } + } + + DebugMsg($"In Bombardment, count: {count}, pos: {pos}, big: {big}", sa); + + Vector2 size = big ? new Vector2(14f) : new Vector2(3f); + + Vector3 position = big ? pos + 3.5f * ExtensionMethods.ToDirection(ExtensionMethods.Round(ev.SourceRotation, 1f)) : pos; + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "Bombardment"; + dp.Color = sa.Data.DefaultDangerColor; + dp.Position = ExtensionMethods.Quantized(position); + dp.Rotation = float.Pi; + dp.Scale = size; + dp.DestoryAt = 9700; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + } + + // // 清理Bombardment AOE + // [ScriptMethod(name: "Bombardment Clear", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43811"])] + // public void BombardmentClear(Event ev, ScriptAccessory sa) + // { + // sa.Method.RemoveDraw("Bombardment.*"); + // _bombardmentAOEs.Clear(); + // _bombardmentBig.Clear(); + // _terrorList.Clear(); + + // DebugMsg("Bombardment cleared", sa); + // } + + + [ScriptMethod(name: "印象-Impression", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43818"])] + public void Impression(Event ev, ScriptAccessory sa) + { + //DrawHelper.DrawDisplacementObject(sa, ev.TargetId, new Vector2(1, 24f), 5200, $"Pungent Aerosol-{ev.SourceId}", , fix: true, drawmode: DrawModeEnum.Default); + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "Impression-knock"; + dp.Color = sa.Data.DefaultSafeColor; + dp.Owner = sa.Data.Me; + dp.TargetPosition = ev.EffectPosition; + dp.Rotation = float.Pi; + dp.Scale = new Vector2(2, 11f); + dp.DestoryAt = 4700; + if (isLead) sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + DrawHelper.DrawCircle(sa, ev.EffectPosition, new Vector2(10f), 4700, "Impression-circle", color: new Vector4(1, 0, 0, ColorAlpha)); + } + + + [ScriptMethod(name: "记忆爆炸-Memory of the Pyre", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43824"])] + public void MemoryofthePyre(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "死刑点名" : "Tankbuster"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "雷光-Keraunography", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43813"])] + public void Keraunography(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawRectObjectNoTarget(sa, ev.SourceId, new Vector2(20, 60), 3700, $"Keraunography-{ev.SourceId}"); + } + + [ScriptMethod(name: "慌乱-Turmoil", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4381[45])$"])] + public void Turmoil(Event ev, ScriptAccessory sa) + { + switch (ev.ActionId) + { + // 左 + case 43814: + { + DrawHelper.DrawFanOwner(sa, ev.SourceId, (-float.Pi / 2), new Vector2(50f), 180, 4700, $"Turmoil-{ev.SourceId}", scaleByTime: false); + string msg = language == Language.Chinese ? "去右侧" : "Go Right"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + break; + } + // 右 + case 43815: + { + DrawHelper.DrawFanOwner(sa, ev.SourceId, (float.Pi / 2), new Vector2(50f), 180, 4700, $"Turmoil-{ev.SourceId}", scaleByTime: false); + string msg = language == Language.Chinese ? "去左侧" : "Go Left"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + break; + } + } + + } + + [ScriptMethod(name: "分摊", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(020D)$"])] + public void MemoryoftheStorm(Event ev, ScriptAccessory sa) + { + string tname = ev["TargetName"]?.ToString() ?? "未知目标"; + if (ev.TargetId == sa.Data.Me) + { + string msg = language == Language.Chinese ? "分摊点名" : "Stack"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } else + { + string msg = language == Language.Chinese ? $"与{tname}分摊" : $"Stack with {tname}"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + DrawHelper.DrawRectObjectTarget(sa, ev.SourceId, ev.TargetId, new Vector2(8, 40), 5000, "MemoryoftheStorm", color: sa.Data.DefaultSafeColor); + + } + + #endregion + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities { get; set; } = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return JsonConvert.DeserializeObject(ev["Index"]); + } +} + +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); + } + } +} +#region 计算函数 + +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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime, false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, MarkType.Attack1, true); + sa.Method.Mark(0xE000000, MarkType.Attack2, true); + sa.Method.Mark(0xE000000, MarkType.Attack3, true); + sa.Method.Mark(0xE000000, MarkType.Attack4, true); + sa.Method.Mark(0xE000000, MarkType.Attack5, true); + sa.Method.Mark(0xE000000, MarkType.Attack6, true); + sa.Method.Mark(0xE000000, MarkType.Attack7, true); + sa.Method.Mark(0xE000000, MarkType.Attack8, true); + sa.Method.Mark(0xE000000, MarkType.Bind1, true); + sa.Method.Mark(0xE000000, MarkType.Bind2, true); + sa.Method.Mark(0xE000000, MarkType.Bind3, true); + sa.Method.Mark(0xE000000, MarkType.Stop1, true); + sa.Method.Mark(0xE000000, MarkType.Stop2, true); + sa.Method.Mark(0xE000000, MarkType.Square, true); + sa.Method.Mark(0xE000000, MarkType.Circle, true); + sa.Method.Mark(0xE000000, MarkType.Cross, true); + sa.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0, DrawModeEnum drawmode = DrawModeEnum.Default) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementObject(ScriptAccessory accessory, ulong target, Vector2 scale, int duration, string name, float rotation, Vector4? color = null, int delay = 0, bool fix = false, DrawModeEnum drawmode = DrawModeEnum.Imgui) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = target; + dp.Scale = scale; + dp.Rotation = rotation; + dp.Delay = delay; + dp.FixRotation = fix; + dp.DestoryAt = duration; + accessory.Method.SendDraw(drawmode, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectNoTarget(ScriptAccessory accessory, ulong owner, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawRectObjectTarget(ScriptAccessory accessory, ulong owner, ulong target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0, ScaleMode scalemode = ScaleMode.None) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.TargetObject = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + dp.ScaleMode = scalemode; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanOwner(ScriptAccessory accessory, ulong owner, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } +} + + +#endregion 函数集 + +#region 扩展方法 +public static class ExtensionMethods +{ + public static float Round(this float value, float precision) => MathF.Round(value / precision) * precision; + + public static Vector3 ToDirection(this float rotation) + { + return new Vector3( + MathF.Sin(rotation), + 0f, + MathF.Cos(rotation) + ); + } + + public static Vector3 Quantized(this Vector3 position, float gridSize = 1f) + { + return new Vector3( + MathF.Round(position.X / gridSize) * gridSize, + MathF.Round(position.Y / gridSize) * gridSize, + MathF.Round(position.Z / gridSize) * gridSize + ); + } +} + +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 + diff --git a/VeeverSW/07-DawnTrail/the-Underkeep.cs b/VeeverSW/07-DawnTrail/the-Underkeep.cs new file mode 100644 index 0000000..c373d09 --- /dev/null +++ b/VeeverSW/07-DawnTrail/the-Underkeep.cs @@ -0,0 +1,861 @@ +using System; +using System.Linq; +using System.Numerics; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using System.Reflection.Metadata; +using System.Net; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Types; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using FFXIVClientStructs; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using FFXIVClientStructs; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace Veever.DawnTrail.the_Underkeep; + +[ScriptType(name: "LV.100 王城古迹永护塔底", territorys: [1266], guid: "9b381347-ddbf-4f52-98a9-a63d6e0d69bd", + version: "0.0.0.5", author: "Veever & Cyf5119", note: noteStr)] + +public class the_Underkeep +{ + const string noteStr = + """ + v0.0.0.5: + 1. 持续更新中 + 2. Boss3的十字炸弹画了但是没画完,先不开放使用 + 3. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 鸭门。 + """; + + [UserSetting("文字横幅提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = true; + + [UserSetting("指路开关")] + public bool isLead { get; set; } = true; + + [UserSetting("标点开关")] + public bool isMark { get; set; } = true; + + [UserSetting("本地标点开关(打开则为本地开关,关闭则为小队)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭")] + public bool isDebug { get; set; } = false; + + public KodakkuAssist.Data.IGameObject? Boss { get; set; } + + public int StaticForceCount; + public int ConcurrentFieldCount; + + public Vector3 Boss1Center = new Vector3(-248.00f, -70.00f, 122.00f); + + private readonly object StaticForceLock = new object(); + private readonly object ConcurrentFieldLock = new object(); + + public void Init(ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + StaticForceCount = 0; + ConcurrentFieldCount = 0; + 补充初始化(accessory); + } + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] + public async void debug(Event @event, ScriptAccessory accessory) + { + //DebugMsg($"Me:{IbcHelper.GetMe().Name}", accessory); + //DebugMsg($"job:{IbcHelper.GetMe().ClassJob.Value.Name}", accessory); + } + + #region 小怪 + [ScriptMethod(name: "Sandstorm", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42904"])] + public void Sandstorm(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Sandstorm-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(15); + dp.Radian = float.Pi / 180 * 90; + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Sandstorm Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42904"], userControl: false)] + public void SandstormClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Sandstorm-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Ultravibration", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42907"])] + public void Ultravibration(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Ultravibration-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Ultravibration Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42907"], userControl: false)] + public void UltravibrationClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Ultravibration-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Sand Crusher", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42905"])] + public void SandCrusher(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"SandCrusher-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(8); + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Sand Crusher Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42905"], userControl: false)] + public void SandCrusherClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"SandCrusher-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Earthquake", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42911"])] + public void Earthquake(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Earthquake-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Earthquake Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42911"], userControl: false)] + public void EarthquakeClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Earthquake-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Piercing Joust", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42917"])] + public void PiercingJoust(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"PiercingJoust-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Piercing Joust Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42917"], userControl: false)] + public void PiercingJoustClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"PiercingJoust-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Blazing Torch", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42921"])] + public void BlazingTorch(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"BlazingTorch-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(10); + dp.Radian = float.Pi / 180 * 120; + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Blazing Torch Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42921"], userControl: false)] + public void BlazingTorchClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"BlazingTorch-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Run Amok", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42924"])] + public void RunAmok(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Run Amok-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(6, 18); + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "Run Amok Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42924"], userControl: false)] + public void RunAmokClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Run Amok-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Wheeling Shot", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42910"])] + public void WheelingShot(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo($"去{@event.TargetName()}身后或打断", duration: 3500, true); + if (isTTS) accessory.Method.TTS($"去{@event.TargetName()}身后或打断"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Wheeling Shot-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(40); + dp.Radian = float.Pi; + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Wheeling Shot Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42910"], userControl: false)] + public void WheelingShotClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Wheeling Shot-{@event.SourceId()}"); + } + + [ScriptMethod(name: "Electrostrike", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42926"])] + public void Electrostrike(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Electrostrike-{@event.SourceId()}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(8); + dp.DestoryAt = 3700; + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Electrostrike Clear", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:42926"], userControl: false)] + public void ElectrostrikeClear(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"Electrostrike-{@event.SourceId()}"); + } + + #endregion + + #region Boss 1 + [ScriptMethod(name: "Boss1 AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(42547|42544)$"])] + public void Boss1AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"AOE"); + } + + [ScriptMethod(name: "Almighty Racket", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42546"])] + public void AlmightyRacket(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("去Boss身后", duration: 3500, true); + if (isTTS) accessory.Method.TTS($"去Boss身后"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Almighty Racket"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(30); + dp.Radian = float.Pi; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Aerial Ambush", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42543"])] + public void AerialAmbush(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("远离直线冲击", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"远离直线冲击"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Aerial Ambush"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = @event.EffectPosition(); + dp.TargetPosition = Boss1Center; + dp.Scale = new Vector2(15, 30); + dp.DestoryAt = 3500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "Sedimentary Debris", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43160"])] + public void SedimentaryDebris(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"分散, 不要重叠"); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Sedimentary Debris"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Boss1死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42548"])] + public void Boss1Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"死刑准备"); + } + + [ScriptMethod(name: "Sphere Shatter", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43135|42545)"])] + public void SphereShatter(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Sphere Shatter"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 1800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + #endregion + + #region Boss 2 + [ScriptMethod(name: "Boss2 AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(42579)$"])] + public void Boss2AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4500, true); + if (isTTS) accessory.Method.TTS($"AOE"); + } + + [ScriptMethod(name: "Boss2死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43136"])] + public void Boss2Tankbuster(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("死刑准备", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"死刑准备"); + } + + public bool isL = true; + + [ScriptMethod(name: "Sector Bisector", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4256[23])$"])] + public void SectorBisector(Event @event, ScriptAccessory accessory) + { + // 42562 left + // 42563 right + if (@event.ActionId() != 42562) + { + isL = false; + } else { + isL = true; + } + + if (isText) accessory.Method.TextInfo($"{(isL ? "去Boss最后一个分身的右侧" : "去Boss最后一个分身的左侧")}", duration: 3500, true); + if (isTTS) accessory.Method.TTS($"{(isL ? "去Boss最后一个分身的右侧" : "去Boss最后一个分身的左侧")}"); + + //var dp = accessory.Data.GetDefaultDrawProperties(); + //dp.Name = $"Sector Bisector"; + //dp.Color = new Vector4(0.0f, 0.749f, 1.0f, 1.0f); + //dp.Owner = @event.TargetId(); + //dp.Scale = new Vector2(45); + //dp.Radian = float.Pi; + //dp.Rotation = isL ? float.Pi / 2 : -float.Pi / 2; + //DebugMsg($"isL: {isL}", accessory); + //DebugMsg($"Rotation: {dp.Rotation}", accessory); + //dp.DestoryAt = 4000; + //accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + // [ScriptMethod(name: "Sector Bisector Shadow", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^()$"])] + // public void SectorBisectorShadow(Event @event, ScriptAccessory accessory) + // { + // // 42562 left + // // 42563 right + // if (@event.ActionId() != 42562) + // { + // isL = false; + // } else { + // isL = true; + // } + + // if (isText) accessory.Method.TextInfo($"{(isL ? "去Boss右侧" : "去Boss左侧")}", duration: 3500, true); + // if (isTTS) accessory.Method.TTS($"{(isL ? "去Boss右侧" : "去Boss左侧")}"); + + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"Sector Bisector"; + // dp.Color = accessory.Data.DefaultDangerColor; + // dp.Owner = @event.TargetId(); + // dp.Scale = new Vector2(45); + // dp.Radian = float.Pi; + // dp.Rotation = isL ? float.Pi / 2 : -float.Pi / 2; + // DebugMsg($"isL: {isL}", accessory); + // DebugMsg($"Rotation: {dp.Rotation}", accessory); + // dp.DestoryAt = 4000; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + // } + + [ScriptMethod(name: "Ordered Fire", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42573"])] + public void OrderedFire(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Ordered Fire"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(8, 55); + if (@event.EffectPosition().Z < -200) + { + dp.Rotation = 0; + } else { + dp.Rotation = float.Pi / 2; + } + dp.DestoryAt = 3500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "Static Force", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:024F"])] + public async void StaticForce(Event @event, ScriptAccessory accessory) + { + await Task.Delay(1000); + lock (StaticForceLock) + { + if (StaticForceCount == 0) + { + DebugMsg($"{StaticForceCount}", accessory); + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"分散, 不要重叠"); + for (var i = 0; i < accessory.Data.PartyList.Count; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Static Force"; + dp.Color = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.TargetObject = accessory.Data.PartyList[i]; + dp.Scale = new Vector2(40); + dp.Radian = float.Pi / 180 * 30; + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Vfx, DrawTypeEnum.Fan, dp); + } + } + StaticForceCount++; + if (StaticForceCount > 3) + { + StaticForceCount = 0; + } + DebugMsg($"StaticForceCount: {StaticForceCount}", accessory); + } + } + + [ScriptMethod(name: "Electric Excess", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43139"])] + public void ElectricExcess(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"分散, 不要重叠"); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Electric Excess"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6); + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + #endregion + + #region Boss 3 + [ScriptMethod(name: "Boss3 AOE", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(42525)$"])] + public void Boss3AOE(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("AOE", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"AOE"); + } + + //[ScriptMethod(name: "Enforcement Ray", eventType: EventTypeEnum.SetObjPos, eventCondition: ["Id:0197"])] + //public void EnforcementRay(Event @event, ScriptAccessory accessory) + //{ + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"Enforcement Ray1-{@event.SourceId()}"; + // dp.Color = accessory.Data.DefaultDangerColor; + // dp.Position = @event.SourcePosition(); + // dp.Scale = new Vector2(60, 4.5f); + // dp.DestoryAt = 8000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Rect, dp); + + // var dp1 = accessory.Data.GetDefaultDrawProperties(); + // dp1.Name = $"Enforcement Ray2-{@event.SourceId()}"; + // dp1.Color = accessory.Data.DefaultDangerColor; + // dp1.Position = @event.SourcePosition(); + // dp1.Scale = new Vector2(60, 4.5f); + // dp1.Rotation = float.Pi; + // dp1.DestoryAt = 8000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Rect, dp1); + + // var dp2 = accessory.Data.GetDefaultDrawProperties(); + // dp2.Name = $"Enforcement Ray3-{@event.SourceId()}"; + // dp2.Color = accessory.Data.DefaultDangerColor; + // dp2.Position = @event.SourcePosition(); + // dp2.Scale = new Vector2(9f, 60f); + // //dp2.Rotation = float.Pi; + // dp2.DestoryAt = 8000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Rect, dp2); + + // var dp3 = accessory.Data.GetDefaultDrawProperties(); + // dp3.Name = $"Enforcement Ray4-{@event.SourceId()}"; + // dp3.Color = accessory.Data.DefaultDangerColor; + // dp3.Position = @event.SourcePosition(); + // dp3.Scale = new Vector2(9f, 60f); + // dp3.Rotation = float.Pi; + // dp3.DestoryAt = 8000; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Rect, dp3); + //} + + [ScriptMethod(name: "Electray", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:43130"])] + public void Electray(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"Electray"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Position = @event.EffectPosition(); + dp.Scale = new Vector2(9, 40); + if (@event.EffectPosition().X > 13) + { + dp.Rotation = -float.Pi / 2; + } + else + { + dp.Rotation = float.Pi / 2; + } + dp.DestoryAt = 3500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "Hypercharged Light", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42524"])] + public void HyperchargedLight(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId() == accessory.Data.Me) + { + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"分散, 不要重叠"); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Hypercharged Light"; + dp.Color = new Vector4(255 / 255.0f, 0 / 255.0f, 251 / 255.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(5); + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Neutralize Front Lines", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42738"])] + public void NeutralizeFrontLines(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Hypercharged Light"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(30); + dp.Radian = float.Pi; + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + + [ScriptMethod(name: "Deterrent Pulse", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:42540"])] + public async void DeterrentPulse(Event @event, ScriptAccessory accessory) + { + string tname = @event["TargetName"]?.ToString() ?? "未知目标"; + + if (isText) accessory.Method.TextInfo($"与{tname}分摊", duration: 4700, true); + if (isTTS) accessory.Method.TTS($"与{tname}分摊"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Deterrent Pulse"; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = @event.SourceId(); + dp.TargetObject = @event.TargetId(); + dp.Scale = new Vector2(8, 40); + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "Concurrent Field", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:024A"])] + public async void ConcurrentField(Event @event, ScriptAccessory accessory) + { + await Task.Delay(1000); + lock (ConcurrentFieldLock) + { + if (ConcurrentFieldCount == 0) + { + DebugMsg($"{ConcurrentFieldCount}", accessory); + if (isText) accessory.Method.TextInfo("分散, 不要重叠", duration: 4000, true); + if (isTTS) accessory.Method.TTS($"分散, 不要重叠"); + for (var i = 0; i < accessory.Data.PartyList.Count; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "Concurrent Field"; + dp.Color = new Vector4(0.0f, 0.749f, 1.0f, 1.0f); + dp.Owner = @event.TargetId(); + dp.TargetObject = accessory.Data.PartyList[i]; + dp.Scale = new Vector2(40); + dp.Radian = float.Pi / 180 * 50; + dp.DestoryAt = 4800; + accessory.Method.SendDraw(DrawModeEnum.Vfx, DrawTypeEnum.Fan, dp); + } + } + ConcurrentFieldCount++; + if (ConcurrentFieldCount > 3) + { + ConcurrentFieldCount = 0; + } + DebugMsg($"StaticForceCount: {ConcurrentFieldCount}", accessory); + } + } + #endregion + + #region 补充 + + private Dictionary _boss2ClonesDict = new(); + + private void 补充初始化(ScriptAccessory sa) + { + _boss2ClonesDict.Clear(); + } + + [ScriptMethod(name: "补充-Boss2分身半场刀连线记录", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0147"], userControl: false)] + public void Boss2ClonesTetherRecord(Event evt, ScriptAccessory sa) + { + _boss2ClonesDict[evt.SourceId()] = evt.TargetId(); + } + + [ScriptMethod(name: "补充-Boss2分身半场刀", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4316[34])$"], suppress: 8000)] + public void Boss2ClonesCleave(Event evt, ScriptAccessory sa) + { + if (!_boss2ClonesDict.ContainsKey(evt.SourceId())) + { + _boss2ClonesDict.Clear(); + return; + } + var lastClone = _boss2ClonesDict[evt.SourceId()]; + var clonesNum = _boss2ClonesDict.Count; + _boss2ClonesDict.Clear(); + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = "Boss2ClonesCleave"; + dp.Color = sa.Data.DefaultDangerColor; + dp.Scale = new Vector2(45); + dp.Radian = float.Pi; + dp.DestoryAt = (clonesNum - 1) * 900 + 500; + dp.Owner = lastClone; + dp.Rotation = evt.ActionId > 43163 ? -float.Pi / 2 : float.Pi / 2; + + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + #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 Id(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id"]); + } + + 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static string DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]) ?? string.Empty; + } + + 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 string Operate(this Event @event) + { + return @event["Operate"]; + } +} + +public static class IbcHelper +{ + public static KodakkuAssist.Data.IGameObject? GetById(ScriptAccessory accessory, uint id) + { + return accessory.Data.Objects.SearchByEntityId(id); + } + + public static KodakkuAssist.Data.IGameObject? GetMe(ScriptAccessory accessory) + { + return accessory.Data.Objects.SearchByEntityId(accessory.Data.Me); + } + + public static KodakkuAssist.Data.IGameObject? GetFirstByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId).FirstOrDefault(); + } + + public static IEnumerable GetByDataId(ScriptAccessory accessory, uint dataId) + { + return accessory.Data.Objects.Where(x => x.DataId == dataId); + } + + public static IEnumerable GetParty(ScriptAccessory accessory) + { + foreach (var pid in accessory.Data.PartyList) + { + var obj = accessory.Data.Objects.SearchByEntityId(pid); + if (obj != null) yield return obj; + } + } + + public static IEnumerable GetPartyEntities(ScriptAccessory accessory) + { + return accessory.Data.Objects.Where(obj => accessory.Data.PartyList.Contains(obj.EntityId)); + } + + public static bool HasStatus(this IBattleChara ibc, uint statusId) + { + return ibc.StatusList.Any(x => x.StatusId == statusId); + } + + public static bool HasStatusAny(this IBattleChara ibc, uint[] statusIds) + { + return ibc.StatusList.Any(x => statusIds.Contains(x.StatusId)); + } + + public static unsafe uint Tethering(this IBattleChara ibc, int index = 0) + { + return ((BattleChara*)ibc.Address)->Vfx.Tethers[index].TargetId.ObjectId; + } +} diff --git a/VeeverSW/07-DawnTrail/the-Wreath-of-Snakes-Unreal.cs b/VeeverSW/07-DawnTrail/the-Wreath-of-Snakes-Unreal.cs new file mode 100644 index 0000000..8a300f1 --- /dev/null +++ b/VeeverSW/07-DawnTrail/the-Wreath-of-Snakes-Unreal.cs @@ -0,0 +1,1956 @@ +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Script; +using Lumina.Excel.Sheets; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Runtime.Intrinsics.Arm; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Veever.DawnTrail.the_Wreath_of_Snakes_Unreal; + +[ScriptType(name: Name, territorys: [825, 1302], guid: "3a915832-971d-4c27-b802-407a1e30ae53", + version: Version, author: "Veever & Usami", note: NoteStr, updateInfo: UpdateStr)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class UnrealSeiryu +{ + const string NoteStr = + $""" + v{Version} + 1. 如果需要某个机制的绘画或者哪里出了问题请在dc@我或者私信我 + 2. 刀禁咒分摊强制绑定画双奶(双奶死了的话就听天由命了) + 3. 塔站位绘图按照如下方式 + MT / D1 ST / D2 + H1 / D3 H2 / D4 + 4. 极神机制还未彻底完成(缺少技能id(其实是懒得去,有朝一日会加的)) + 5. 鲶鱼精标点为基础北东南西ABCD标点 + 鸭门 + ---------------------------------- + 1. If you need a draw or notice any issues, @ me on DC or DM me. + 2. For the Forbidden Arts Stack, both healers are hard-locked draw + (if both healers die, it’s GG / pray to Hydaelyn xd). + 3. Tower positions are as follows: + MT / D1 ST / D2 + H1 / D3 H2 / D4 + 4. EX trial draw are not fully implemented yet (missing skill IDs — honestly just lazy, + will add them one day). + 5. PostNamazu markers default to standard N/E/S/W (ABCD). + Duckmen. + """; + + const string UpdateStr = + $""" + v{Version} + 修复了TTS + 鸭门 + ---------------------------------- + Fixed TTS + Duckmen. + """; + + private const string Name = "LV.100 青龙诗魂-幻巧战 [the Wreath of Snakes Ex-Unreal]"; + private const string Version = "0.0.0.2"; + private const string DebugVersion = "a"; + + private const bool Debugging = false; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static readonly Vector3 Center = new Vector3(100, 0, 100); + + // TODO 根据第一个AOE的读条调整 + private static uint BossDataId = 9708; + + [UserSetting("播报语言(language)")] + public Language language { get; set; } = Language.Chinese; + + [UserSetting("绘图不透明度,数值越大越显眼(Draw opacity — higher value = more visible)")] + public static float ColorAlpha { get; set; } = 1f; + + [UserSetting("文字横幅提示开关(Banner text toggle)")] + public bool isText { get; set; } = true; + + [UserSetting("TTS开关(TTS toggle)")] + public bool isTTS { get; set; } = true; + + [UserSetting("是否自动使用防击退(Auto anti-knockback)")] + public bool useaction { get; set; } = true; + + [UserSetting("指路开关(Guide arrow toggle)")] + public bool isLead { get; set; } = true; + + [UserSetting("目标标记开关(Target Marker toggle)")] + public bool isMark { get; set; } = true; + + [UserSetting("本地目标标记开关(打开则为本地开关,关闭则为小队) - Local target marker toggle (ON = local only, OFF = party shared)")] + public bool LocalMark { get; set; } = true; + + [UserSetting("是否进行场地标点引导(Waymark guide toggle)")] + public bool PostNamazuPrint { get; set; } = true; + + [UserSetting("鲶鱼精邮差端口设置(PostNamazuPort Setting)")] + public int PostNamazuPort { get; set; } = 2019; + + [UserSetting("场地标点是否为本地标点(如果选择非本地标点,脚本只会在非战斗状态下进行标点) - Waymarks: local toggle(off = party shared, OOC only)")] + public bool PostNamazuisLocal { get; set; } = true; + + [UserSetting("Debug开关, 非开发用请关闭 - Debug on/off (don't touch unless you know what you're doing)")] + public bool isDebug { get; set; } = false; + + public enum Language + { + Chinese, + English + } + + private volatile List _bools = new bool[20].ToList(); // 被记录flag + private List _numbers = Enumerable.Repeat(0, 8).ToList(); + private static List _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + + private Dictionary _tethersDict = new(); + private static bool _initHint = false; + public int KanaboTTSCount; + + private enum SeiryuPhase + { + Init, + P2A_Mobs, // P2A 小怪 + P3A_RainStorm, // P3A 暴雨 + P3B_BrazenSoul, // P3B 荒魂 + } + + private static SeiryuPhase _seiryuPhase = SeiryuPhase.Init; + + public void DebugMsg(string str, ScriptAccessory accessory) + { + if (!isDebug) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public void Init(ScriptAccessory sa) + { + _seiryuPhase = SeiryuPhase.Init; + RefreshParams(); + sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + if (PostNamazuPrint) PostWaymark(sa); + KanaboTTSCount = 0; + _initHint = false; + } + + private void RefreshParams() + { + _bools = new bool[20].ToList(); + _numbers = Enumerable.Repeat(0, 20).ToList(); + _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + _tethersDict = new Dictionary(); + } + + + + #region Waymark + private static readonly Vector3 posA = new Vector3(100.00f, -0.00f, 80.86f); + private static readonly Vector3 posB = new Vector3(119.28f, -0.00f, 100.00f); + private static readonly Vector3 posC = new Vector3(100.00f, -0.00f, 119.03f); + private static readonly Vector3 posD = new Vector3(80.51f, -0.00f, 100.00f); + + + public void PostWaymark(ScriptAccessory accessory) + { + var waymark = new NamazuHelper.Waymark(accessory); + waymark.AddWaymarkType("A", posA); + waymark.AddWaymarkType("B", posB); + waymark.AddWaymarkType("C", posC); + waymark.AddWaymarkType("D", posD); + + waymark.SetJsonPayload(LocalMark, PostNamazuisLocal); + waymark.PostWaymarkCommand(PostNamazuPort); + } + #endregion + + [ScriptMethod(name: "主动进行场地标点 - Place waymarks", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdk"], + userControl: true)] + public void userNamazuPost(Event ev, ScriptAccessory sa) + { + PostWaymark(sa); + } + + + [ScriptMethod(name: "---- 时间轴记录 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void SplitLine_Timeline(Event ev, ScriptAccessory sa) + { + // DataId + // 9708 青龙 + // 9710 红之式鬼 + // 9711 苍之式鬼 + // 9712 岩之式鬼 + // 10103 天之式鬼 + // 9714 泥之式鬼(小) + // 9713 沼之式鬼(大) + // 9715 山之式鬼 + + // SkillId + // - 14275 AOE 阴阳五行 + // - 14301 圆形点名 降蛇 + // - 14849 钢铁 阴阳之印 + // - 14305 读条 九子切 + // - 14306 九子切直线 阳之刀印 + // - 14290 咒怨的替身 + // - 14292 死刑 虚证弹 + // - 14291 诅咒返还 + + // - 14286 召唤小怪 式鬼召唤 + // - 14321 赤突进 + // - 14320 青突进 + // - 14317 大钢铁 百吨回转 + // - 15393 直线 山神乐 + // - 14356 接线扇形 如虎添翼 + // 14324 需插言打断 石肤 + // - 14322 爆炸(沼) + // - 14323 爆炸(泥) + // - 14281 转场 灵气 + // - 14282 玩家眩晕时 云蒸龙变 ActionEffect + // - 14283 玩家眩晕时 云蒸龙变 23s + + // - 14328 荒波(扇形) 4s + // - 14329 荒波(月环) 3s + // - 14330 荒波(月环) 3s + // - 14331 荒波(月环) 3s + // - 14288 召唤小怪 式鬼召唤 + // - 14326 蛇崩 7.5s 这个技能让玩家受伤 + // - 14327 蛇崩 7.5s 使用这个技能的在场边 + // - 14325 蛇崩 5s + // - 14626 蛇崩 5s + // - 14627 蛇崩 5s + // - 14287 召唤小怪 式鬼召唤 + + // - 14312 压杀掌 读条 + // - 14311 压杀掌 读条 + // - 14309 压杀掌 ActionEffect 右手 + // - 14310 压杀掌 ActionEffect 左手 + // - 14315 大压杀 + // - 14314 大压杀 + // - 14313 大压杀 ActionEffect + + // 14277 刀禁咒 分摊 + // 14279 刀禁咒 伤害 #1 + // 14280 刀禁咒 伤害 #2 + + // 15394 刀禁咒 伤害(荒魂) #1 + // 15395 刀禁咒 伤害(荒魂) #2 + + // 14308 荒魂燃烧 + // - 14851 钢铁(荒魂) 阴阳之印(先发) + // - 14852 月环(荒魂) 蛇眼之印(后发) + // - 14853 月环(荒魂) 蛇眼之印(先发) + // - 14854 钢铁(荒魂) 阴阳之印(后发) + // - 14307 九子切直线 阴之刀印 + + // - 15397 升龙 + // - 14302 原地点名黄圈 降蛇 + // - MorelogCompat EventObj 出塔 + + // - 14285 召唤小怪 式鬼召唤 + + // StatusId + // 1725 咒怨的替身 Buff + // 1696 淹没 无法移动与使用技能 + // 1054 受伤加重 + + // TetherId + // 0039 苍之式鬼 分摊 + // 0011 红之式鬼 连线击退 + // 0054 岩之式鬼 接线扇形 + } + + [ScriptMethod(name: "---- 测试项 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 测试项(Event ev, ScriptAccessory sa) + { + var bossId = GetBossObject(sa).GameObjectId; + var dp = sa.DrawCircle(bossId, 0, 3000, $"钢铁", 12f, byTime: true, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + sa.ScaleModify(GetBossObject(sa), 2f); + } + + [ScriptMethod(name: "策略与身份提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14275|43962)$"], + userControl: true)] + public void MethodRoleTips(Event ev, ScriptAccessory sa) + { + KanaboTTSCount = 0; + if (_initHint) return; + _initHint = true; + + // 根据第一个AOE的读条,确定是极青龙还是幻青龙 + var aid = ev.ActionId; + BossDataId = aid == 43962u ? 18643u : 9708u; + + var myIndex = sa.Data.PartyList.IndexOf(sa.Data.Me); + List role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + sa.Method.TextInfo( + $"你是【{role[myIndex]}】," + + $"若有误请及时调整。", 5000); + } + + [ScriptMethod(name: "AOE提示-AOE warnings", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(43962)$"], + userControl: true)] + public void AOENotify(Event ev, ScriptAccessory sa) + { + string msg = language == Language.Chinese ? "AOE" : "AOE"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + [ScriptMethod(name: "九字切绘图 - Kuji-kiri Draw", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4399[3])$"], + userControl: true)] + public void bladeSigil(Event ev, ScriptAccessory sa) + { + // 1.57 -> 东西 + // 0 -> 南北 + Vector3 TargetPos0 = ev.TargetPosition; + Vector3 TargetPos1 = ev.TargetPosition; + DebugMsg($"TargetPos0: {TargetPos0}, TargetPos1: {TargetPos1}, rotation: {ev.SourceRotation}", sa); + //DrawHelper.DrawRect(sa, ev.SourcePosition, ) + if (ev.SourceRotation == 1.57f) + { + DebugMsg($"X change", sa); + TargetPos0.X += 30f; + TargetPos1.X -= 30f; + } + else if (ev.SourceRotation == 0f) + { + DebugMsg($"Z change", sa); + TargetPos0.Z += 30f; + TargetPos1.Z -= 30f; + } + + DrawHelper.DrawRect(sa, ev.SourcePosition, TargetPos0, new Vector2(4f, 50f), 3000, $"bladeSigil0-{ev.SourceId}", color: new Vector4(0, 1, 1, ColorAlpha)); + DrawHelper.DrawRect(sa, ev.SourcePosition, TargetPos1, new Vector2(4f, 50f), 3000, $"bladeSigil1-{ev.SourceId}", color: new Vector4(0, 1, 1, ColorAlpha)); + } + + [ScriptMethod(name: "本体:钢铁月环", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(148(49|51|53)|4402[468])$"], + userControl: true)] + public void 钢铁月环(Event ev, ScriptAccessory sa) + { + // OnmyoSigil + var aid = ev.ActionId; + var sid = ev.SourceId; + sa.Log.Debug($"检测到 钢铁月环 {aid},执行绘图"); + + DrawPropertiesEdit? dp, dp2; + + switch (aid) + { + case 14849 or 44024: + dp = sa.DrawCircle(sid, 0, 3000, $"钢铁", 12f, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + string msg = language == Language.Chinese ? "钢铁远离" : "Chariot - Stay away from boss"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 2700, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + break; + + case 14853 or 44028: + string msg1 = language == Language.Chinese ? "先月环后钢铁" : "Dynamo → Chariot"; + if (isText) sa.Method.TextInfo($"{msg1}", duration: 2700, true); + if (isTTS) sa.Method.TTS($"{msg1}"); + + dp = sa.DrawDonut(sid, 0, 3000, $"月环", 30f, 7f, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + + dp2 = sa.DrawCircle(sid, 3000, 3000, $"钢铁", 12f, draw: false); + dp2.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + break; + + case 14851 or 44026: + string msg2 = language == Language.Chinese ? "先钢铁后月环" : "Chariot → Dynamo"; + if (isText) sa.Method.TextInfo($"{msg2}", duration: 2700, true); + if (isTTS) sa.Method.TTS($"{msg2}"); + + dp = sa.DrawCircle(sid, 0, 3000, $"钢铁", 12f, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp2 = sa.DrawDonut(sid, 3000, 3000, $"月环", 30f, 7f, draw: false); + dp2.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp2); + break; + + default: + break; + } + } + + [ScriptMethod(name: "本体:咒怨的替身 + 死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14290|43977)$"], + userControl: true)] + public async void 咒怨的替身与死刑(Event ev, ScriptAccessory sa) + { + var myIndex = sa.Data.PartyList.IndexOf(sa.Data.Me); + // InfirmSoul + var aid = ev.ActionId; + sa.Log.Debug($"检测到 咒怨的替身 {aid},执行绘图"); + var sid = ev.SourceId; + var tid = ev.TargetId; + + // 对当前一仇绘图,表示接下来的死刑目标 + var dp = sa.DrawCircle(sid, 0, 10000, $"死刑目标", 4f, byTime: false, draw: false); + dp.SetOwnersEnmityOrder(1); + dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + + // 对当前施法目标绘图,表示Debuff目标,不可吃死刑 + var dp0 = sa.DrawCircle(tid, 0, 10000, $"Debuff目标", 4f, byTime: false, draw: false); + dp0.Color = new Vector4(0f, 0f, 1f, ColorAlpha); + + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + + if (myIndex == 0 || myIndex == 1) + { + string msg = language == Language.Chinese ? "换T" : "Tank swap"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + await Task.Delay(13000); + string msg1 = language == Language.Chinese ? "换T" : "Tank swap"; + if (isText) sa.Method.TextInfo($"{msg1}", duration: 3000); + if (isTTS) sa.Method.TTS($"{msg1}"); + } + } + + [ScriptMethod(name: "本体:咒怨的替身 + 死刑(绘图删除)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(14292|43979)$"], + userControl: Debugging)] + public void 咒怨的替身与死刑_绘图删除(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"死刑目标"); + sa.Method.RemoveDraw($"Debuff目标"); + } + + [ScriptMethod(name: "--- 式鬼召唤一 阶段转换 ---", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14286|43973)$"], + userControl: Debugging)] + public void 式鬼召唤一_阶段转换(Event ev, ScriptAccessory sa) + { + // SummonShiki + sa.Log.Debug($"检测到 式鬼召唤一 {ev.ActionId}。"); + _seiryuPhase = SeiryuPhase.P2A_Mobs; + RefreshParams(); + sa.Log.Debug($"阶段转换为:{_seiryuPhase}"); + } + + [ScriptMethod(name: "红轮:赤突进", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(0011)$"], + userControl: true)] + public void 赤突进(Event ev, ScriptAccessory sa) + { + // RedRush + sa.Log.Debug($"检测到 赤突进 连线 {ev.Id0()},连线目标 {ev.SourceId}, {ev.TargetId}。"); + + // 分出玩家与轮轮 + var playerId = sa.Data.PartyList.Contains((uint)ev.SourceId) ? ev.SourceId : ev.TargetId; + var bossId = playerId == ev.SourceId ? ev.TargetId : ev.SourceId; + + var dp = sa.DrawRect(bossId, playerId, 0, 10000, $"赤突进", + 0, 5f, 10, false, false, true, false); + dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + lock (_numbers) + { + // 自身为目标时出现击退 + if (ev.TargetId == sa.Data.Me) + { + _bools[0] = true; + string msg = language == Language.Chinese ? "两侧引导,不要冲击人群" : "Go to flanks, avoid cleaving"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + + if (useaction) sa.Method.UseAction(sa.Data.Me, 7559); + if (useaction) sa.Method.UseAction(sa.Data.Me, 7548); + } + + + _numbers[0]++; + sa.Log.Debug($"已检测 赤突进 #{_numbers[0]}"); + + if (_numbers[0] == 2) + { + _events[0].Set(); + sa.Log.Debug($"赤突进记录成功,释放锁"); + } + } + + if (ev.TargetId != sa.Data.Me) return; + var dp0 = sa.DrawKnockBack(bossId, 0, 10000, $"赤突进击退", + 3f, 15, false, false); + dp0.Color = new Vector4(0f, 1f, 1f, ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp0); + } + + [ScriptMethod(name: "蓝轮:青突进", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(0039)$"], + userControl: true)] + public void 青突进(Event ev, ScriptAccessory sa) + { + // BlueBolt + sa.Log.Debug($"检测到 青突进 连线 {ev.Id0()},连线目标 {ev.SourceId}, {ev.TargetId}。"); + _events[0].WaitOne(); + sa.Log.Debug($"青突进解锁成功,玩家{(_bools[0] ? "避开分摊" : "参与分摊")}"); + + // 分出玩家与轮轮 + var playerId = sa.Data.PartyList.Contains((uint)ev.SourceId) ? ev.SourceId : ev.TargetId; + var bossId = playerId == ev.SourceId ? ev.TargetId : ev.SourceId; + + var dp = sa.DrawRect(bossId, playerId, 0, 6000, $"青突进", + 0, 5f, 40, false, false, false, false); + dp.Color = _bools[0] + ? sa.Data.DefaultDangerColor.WithW(ColorAlpha) + : sa.Data.DefaultSafeColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + _events[0].Reset(); + } + + + [ScriptMethod(name: "式鬼-百吨回转", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44004)$"], + userControl: true)] + public void tonzeSwing(Event ev, ScriptAccessory sa) + { + DrawHelper.DrawCircleObject(sa, ev.SourceId, new Vector2(16f), 3700, "100-tonzeSwing"); + } + + [ScriptMethod(name: "赤突进、青突进(绘图删除)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(1432[01]|4400[78])$"], + userControl: Debugging)] + public void 青突进绘图删除(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"赤突进.*"); + sa.Method.RemoveDraw($"青突进.*"); + } + + [ScriptMethod(name: "石头人:如虎添翼", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(0054)$"], + userControl: true)] + public void Kanabo(Event ev, ScriptAccessory sa) + { + // Kanabo + sa.Log.Debug($"检测到 如虎添翼 连线 {ev.Id0()},连线目标 {ev.SourceId}, {ev.TargetId}。"); + var bossId = ev.SourceId; + var playerId = ev.TargetId; + var myIndex = sa.Data.PartyList.IndexOf(sa.Data.Me); + + if (myIndex == 0 && KanaboTTSCount == 0) + { + string msg = language == Language.Chinese ? "接场地西侧的线,并引导至场外" : "Take WEST line, drag out"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + KanaboTTSCount++; + } + + if (myIndex == 1 && KanaboTTSCount == 0) + { + string msg = language == Language.Chinese ? "接场地东侧的线,并引导至场外" : "Take EAST line, drag out"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + KanaboTTSCount++; + } + + + var oldTargetId = _tethersDict.GetValueOrDefault(bossId, 0u); + sa.Log.Debug($"{bossId}的前一任目标为{oldTargetId}。"); + if (oldTargetId == playerId) return; + + sa.Method.RemoveDraw($"如虎添翼{bossId}_{oldTargetId}"); + _tethersDict[bossId] = playerId; + + var dp = sa.DrawFan(bossId, playerId, 0, 10000, $"如虎添翼{bossId}_{playerId}", + 60f.DegToRad(), 0, 40f, 0f, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "石头人:如虎添翼(绘图删除)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(14318|44005)$"], + userControl: Debugging)] + public void 如虎添翼绘图删除(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"如虎添翼.*"); + } + + [ScriptMethod(name: "泥巴人:石肤提示与本地标点", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14324|44011)$"], + userControl: true)] + public void 石肤(Event ev, ScriptAccessory sa) + { + sa.Log.Debug($"检测到 {ev.SourceId} 正在释放 石肤 {ev.ActionId}。"); + var sid = ev.SourceId; + var myIndex = sa.Data.PartyList.IndexOf(sa.Data.Me); + var dp = sa.DrawCircle(sid, 0, 5000, $"石肤{sid}", 3f, draw: false); + dp.Color = new Vector4(1f, 0f, 0f, ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (myIndex == 0) + { + string msg = language == Language.Chinese ? "沉默打断被标记式鬼" : "Silence the marked mob!"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 3000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + if (isMark) sa.Method.Mark((uint)sid, MarkType.Attack1, LocalMark); + } + + [ScriptMethod(name: "泥巴人:石肤,技能打断或释放成功后恢复", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(14324|44011)$"], + userControl: Debugging)] + public void 石肤恢复1(Event ev, ScriptAccessory sa) + { + sa.Log.Debug($"检测到 {ev.SourceId} 释放完毕了 石肤 {ev.ActionId}。"); + var sid = ev.SourceId; + sa.MarkClear(local: true); + sa.Method.RemoveDraw($"石肤{sid}"); + } + + [ScriptMethod(name: "泥巴人:石肤,技能打断或释放成功后恢复", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:regex:^(14324|44011)$"], + userControl: Debugging)] + public void 石肤恢复2(Event ev, ScriptAccessory sa) + { + sa.Log.Debug($"检测到 {ev.SourceId} 释放完毕了 石肤 {ev.ActionId}。"); + var sid = ev.SourceId; + sa.MarkClear(local: true); + sa.Method.RemoveDraw($"石肤{sid}"); + } + + [ScriptMethod(name: "--- 灵气 阶段转换 ---", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14281|43968)$"], + userControl: Debugging)] + public void 灵气_阶段转换(Event ev, ScriptAccessory sa) + { + // SummonShiki + sa.Log.Debug($"检测到 灵气 {ev.ActionId}。"); + _seiryuPhase = SeiryuPhase.P3A_RainStorm; + RefreshParams(); + sa.Log.Debug($"阶段转换为:{_seiryuPhase}"); + } + + [ScriptMethod(name: "后半:蛇崩击退", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14327|44014)$"], + userControl: true)] + public void 蛇崩击退(Event ev, ScriptAccessory sa) + { + // CoursingRiver + sa.Log.Debug($"检测到 蛇崩 {ev.ActionId},释放位置 {ev.EffectPosition}"); + + var dir = ev.EffectPosition.GetRadian(Center).RadianToRegion(4, isDiagDiv: true); + // 1则蛇崩在右,向左击退;3则蛇崩在左,向右击退 + sa.Log.Debug($"蛇崩释放方位 {dir} (1右3左)"); + + var dp = sa.DrawRect(sa.Data.Me, 0, 10000, $"蛇崩", + -dir * float.Pi/2, 5f, 25f, draw: false); + dp.FixRotation = true; + dp.Color = new Vector4(0f, 1f, 1f, ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + var knockBackPos = new Vector3(100, 0, 107).RotateAndExtend(Center, dir * float.Pi / 2, 0); + sa.Log.Debug($"蛇崩击退位置 {knockBackPos}"); + var dp0 = sa.DrawGuidance(knockBackPos, 0, 10000, $"蛇崩指路", draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + + [ScriptMethod(name: "后半:蛇崩击退(绘图删除)", eventType: EventTypeEnum.KnockBack, eventCondition: ["Distance:regex:^(25.00)$"], + userControl: Debugging)] + public void 蛇崩击退删除(Event ev, ScriptAccessory sa) + { + sa.Log.Debug($"捕捉到 25.00 的击退距离。"); + sa.Method.RemoveDraw($"蛇崩.*"); + } + + + [ScriptMethod(name: "山鬼:压杀掌", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(143(09|10)|43999)$"], + userControl: true)] + public void 压杀掌(Event ev, ScriptAccessory sa) + { + + sa.Log.Debug($"检测到 压杀掌 {ev.ActionId}。"); + var aid = ev.ActionId; + + //var dp = sa.DrawFan(Center, 0, 4200, $"压杀掌{aid}", + // 180f.DegToRad(), ev.SourceRotation, 20f, 0f, draw: false); + + //dp.Color = sa.Data.DefaultDangerColor.WithW(ColorAlpha); + //sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + + + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = $"压杀掌{aid}"; + dp.Color = sa.Data.DefaultDangerColor; + dp.Owner = ev.SourceId; + dp.Scale = new Vector2(20f); + dp.Radian = 180 * (float.Pi / 180); + dp.DestoryAt = 4200; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + // TODO: which one? + [ScriptMethod(name: "山鬼:压杀掌(绘图删除)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(14312)$"], + userControl: Debugging)] + public void 压杀掌绘图删除(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"压杀掌.*"); + } + + [ScriptMethod(name: "--- 荒魂 阶段转换 ---", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14308|43995)$"], + userControl: Debugging)] + public void 荒魂_阶段转换(Event ev, ScriptAccessory sa) + { + // Blazing Aramitama + sa.Log.Debug($"检测到 荒魂燃烧 {ev.ActionId}。"); + _seiryuPhase = SeiryuPhase.P3B_BrazenSoul; + RefreshParams(); + sa.Log.Debug($"阶段转换为:{_seiryuPhase}"); + } + + [ScriptMethod(name: "荒魂:升龙集合提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(15397|44034)$"], + userControl: true)] + public void 升龙集合提示(Event ev, ScriptAccessory sa) + { + sa.Log.Debug($"检测到 升龙 {ev.ActionId}。"); + Vector3 midpos = new Vector3(100, 0, 100); + var dp = sa.DrawGuidance(midpos, 0, 3000, $"集合点"); + + string msg = language == Language.Chinese ? "集合引导放黄圈" : "Stack bait"; + if (isText) sa.Method.TextInfo($"{msg}", duration: 4000, true); + if (isTTS) sa.Method.TTS($"{msg}"); + } + + public bool towered = true; + + [ScriptMethod(name: "荒魂:降蛇踩塔指路", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(00A9)$"], + userControl: true)] + public void towerUpdate(Event ev, ScriptAccessory sa) + { + if (ev.TargetId == sa.Data.Me) + { + towered = false; + } + else + { + towered = true; + } + } + + [ScriptMethod(name: "荒魂:降蛇踩塔指路", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:regex:^(2009660)$", "Operate:Add"], + userControl: true, suppress: 10000)] + public void 降蛇踩塔指路(Event ev, ScriptAccessory sa) + { + var myIndex = sa.GetMyIndex(); + List towerPos = [new(88.7f, 0, 88.7f), new(111.3f, 0, 88.7f), new(88.7f, 0, 111.3f), new (111.3f, 0, 111.3f)]; + List notowerPos = [new(100.28f, 0.01f, 86.13f), new(114.16f, 0.01f, 99.95f), new(86.70f, 0.01f, 100.50f), new(99.98f, 0.01f, 115.13f)]; + DebugMsg($"towered: {towered}", sa); + if (towered) + { + var dp = sa.DrawGuidance(towerPos[myIndex % 4], 0, 10000, $"降蛇踩塔"); + } else + { + var dp = sa.DrawGuidance(notowerPos[myIndex % 4], 0, 10000, $"降蛇踩塔"); + } + + } + + [ScriptMethod(name: "荒魂:降蛇踩塔指路(绘图删除1)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(14301|43988)$"], + userControl: Debugging)] + public void 降蛇踩塔指路绘图删除1(Event ev, ScriptAccessory sa) + { + // 因被点名降蛇,删除踩塔指路 + var tid = ev.TargetId; + var aid = ev.ActionId; + sa.Log.Debug($"检测到 降蛇 {aid},目标为 {tid} ({sa.GetPlayerIdIndex((uint)tid)})。"); + + if (tid != sa.Data.Me) return; + sa.Method.RemoveDraw($"降蛇踩塔.*"); + } + + [ScriptMethod(name: "荒魂:降蛇踩塔指路(绘图删除2)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(14301|43988)$"], + userControl: Debugging)] + public void 降蛇踩塔指路绘图删除2(Event ev, ScriptAccessory sa) + { + // 因降蛇判定,删除踩塔指路 + sa.Method.RemoveDraw($"降蛇踩塔.*"); + } + + [ScriptMethod(name: "荒魂:刀禁咒分摊", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(14277|43964)$"], + userControl: true)] + public void 刀禁咒分摊(Event ev, ScriptAccessory sa) + { + // ForbiddenArts + var aid = ev.ActionId; + var sid = ev.SourceId; + var tid = ev.TargetId; + sa.Log.Debug($"检测到 刀禁咒 {aid},目标为 {tid} ({sa.GetPlayerIdIndex((uint)tid)})。"); + + // TODO 如何确定第二个分摊目标?如果少人点谁? // 干就完了,管他死没死 + var dp = sa.DrawRect(sid, sa.Data.PartyList[2], 0, 6000, $"青突进", + 0, 5f, 40, false, false, false, false); + dp.Color = sa.Data.DefaultSafeColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + var dp1 = sa.DrawRect(sid, sa.Data.PartyList[3], 0, 6000, $"青突进", + 0, 5f, 40, false, false, false, false); + dp1.Color = sa.Data.DefaultSafeColor.WithW(ColorAlpha); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp1); + } + + private static IGameObject? GetBossObject(ScriptAccessory sa) + { + return sa.GetByDataId(BossDataId).FirstOrDefault(); + } + + #region 优先级字典 类 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa {get; set;} = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities {get; set;} = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + public void Init(ScriptAccessory accessory, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + sa = accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int idx, int priority) + { + Priorities[idx] += priority; + } + + /// + /// 从Priorities中找到前num个数值最小的,得到新的Dict返回 + /// + /// + /// + public List> SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Priorities中找到前num个数值最大的,得到新的Dict返回 + /// + /// + /// + public List> SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Priorities中找到升序排列中间的数值,得到新的Dict返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// + /// 降序排列,默认为false + /// + public List> SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Priorities.Count < skip + num) + return new List>(); + + IEnumerable> sortedPriorities; + if (descending) + { + // 根据值从大到小降序排序,并取前num个键 + sortedPriorities = Priorities + .OrderByDescending(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + else + { + // 根据值从小到大升序排序,并取前num个键 + sortedPriorities = Priorities + .OrderBy(pair => pair.Value) // 先根据值排列 + .ThenBy(pair => pair.Key) // 再根据键排列 + .Skip(skip) // 跳过前skip个元素 + .Take(num); // 取前num个键值对 + } + + return sortedPriorities.ToList(); + } + + /// + /// 从Priorities中找到升序排列第idx位的数据,得到新的Dict返回 + /// + /// + /// 降序排列,默认为false + /// + public KeyValuePair SelectSpecificPriorityIndex(int idx, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, Priorities.Count, descending); + var i = 0; + foreach (var dict in sortedPriorities) + { + if (dict.Key == key) return i; + i++; + } + + return i; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Priorities.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Priorities.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showJob = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Priorities.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} {(showJob ? $"({Role[pair.Key]})" : "")}, Value {pair.Value}\n"; + } + + return str; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + } + + #endregion 优先级字典 类 + +} + +#region 函数集 +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 Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return JsonConvert.DeserializeObject(ev["Index"]); + } +} + +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); + } + } +} +#region 计算函数 + +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()); + } +} + +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory sa, uint pid) + { + // 获得玩家 IDX + return sa.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory sa, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = sa.Data.PartyList.IndexOf(pid); + var str = sa.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory sa, int idx, bool fourPeople = false) + { + List role8 = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + List role4 = ["T", "H", "D1", "D2"]; + if (idx < 0 || idx >= 8 || (fourPeople && idx >= 4)) + return "Unknown"; + return fourPeople ? role4[idx] : role8[idx]; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory sa, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return sa.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +public static class DrawTools +{ + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为UID或位置 + /// 绘图指向目标,可为UID或位置 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘制图形弧度范围 + /// 绘制图形旋转弧度,以owner面前为基准,逆时针增加 + /// 绘制图形宽度,部分图形可保持与长度一致 + /// 绘制图形长度,部分图形可保持与宽度一致 + /// 绘制图形内宽,部分图形可保持与长度一致 + /// 绘制图形内长,部分图形可保持与宽度一致 + /// 绘图方式 + /// 绘图类型 + /// 是否使用安全色 + /// 动画效果随时间填充 + /// 动画效果随距离变更 + /// 是否直接绘图 + /// + public static DrawPropertiesEdit DrawOwnerBase(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float radian, float rotation, float width, float length, float innerWidth, float innerLength, + DrawModeEnum drawModeEnum, DrawTypeEnum drawTypeEnum, bool isSafe = false, + bool byTime = false, bool byY = false, bool draw = true) + { + var dp = sa.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.InnerScale = new Vector2(innerWidth, innerLength); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Color = isSafe ? sa.Data.DefaultSafeColor: sa.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + dp.ScaleMode |= byY ? ScaleMode.YByDistance : ScaleMode.None; + + switch (ownerObj) + { + case uint u: + dp.Owner = u; + break; + case ulong ul: + dp.Owner = ul; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException($"ownerObj {ownerObj} 的目标类型 {ownerObj.GetType()} 输入错误"); + } + + switch (targetObj) + { + case 0: + case 0u: + break; + case uint u: + dp.TargetObject = u; + break; + case ulong ul: + dp.TargetObject = ul; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + default: + throw new ArgumentException($"targetObj {targetObj} 的目标类型 {targetObj.GetType()} 输入错误"); + } + + if (draw) + sa.Method.SendDraw(drawModeEnum, drawTypeEnum, dp); + return dp; + } + + /// + /// 返回指路绘图 + /// + /// + /// 出发点 + /// 结束点 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头旋转角度 + /// 箭头宽度 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, + float rotation = 0, float width = 1f, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, isSafe, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float rotation = 0, float width = 1f, bool isSafe = true, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, rotation, width, isSafe, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, DrawModeEnum.Default, DrawTypeEnum.Circle, isSafe, byTime,false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Donut, isSafe, byTime, false, draw); + + /// + /// 返回扇形绘图 + /// + /// + /// 圆心 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 弧度 + /// 旋转角度 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, DrawTypeEnum.Fan, isSafe, byTime, false, draw); + + public static DrawPropertiesEdit DrawFan(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float radian, float rotation, + float outScale, float innerScale, bool isSafe = false, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, isSafe, byTime, draw); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Rect, isSafe, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, isSafe, byTime, byY, draw); + + /// + /// 返回背对绘图 + /// + /// + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, bool isSafe = true, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, 0, 0, 0, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.SightAvoid, isSafe, false, false, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, isSafe, false, false, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, bool isSafe = false, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Line, isSafe, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 是否安全色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, float width = 1f, bool isSafe = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, isSafe, false, true, draw); + + /// + /// 赋予输入的dp以ownerId为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetOwnersDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.CentreResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以position为源的远近目标绘图 + /// + /// + /// 从owner计算,近顺序或远顺序 + /// 从1开始 + /// + public static DrawPropertiesEdit SetPositionDistanceOrder(this DrawPropertiesEdit self, bool isNearOrder, + uint orderIdx) + { + self.TargetResolvePattern = isNearOrder + ? PositionResolvePatternEnum.PlayerNearestOrder + : PositionResolvePatternEnum.PlayerFarestOrder; + self.TargetOrderIndex = orderIdx; + return self; + } + + /// + /// 赋予输入的dp以ownerId施法目标为源的绘图 + /// + /// + /// + public static DrawPropertiesEdit SetOwnersTarget(this DrawPropertiesEdit self) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +public static class MarkerHelper +{ + public static void LocalMarkClear(this ScriptAccessory sa) + { + sa.Log.Debug($"删除本地标点。"); + sa.Method.Mark(0xE000000, MarkType.Attack1, true); + sa.Method.Mark(0xE000000, MarkType.Attack2, true); + sa.Method.Mark(0xE000000, MarkType.Attack3, true); + sa.Method.Mark(0xE000000, MarkType.Attack4, true); + sa.Method.Mark(0xE000000, MarkType.Attack5, true); + sa.Method.Mark(0xE000000, MarkType.Attack6, true); + sa.Method.Mark(0xE000000, MarkType.Attack7, true); + sa.Method.Mark(0xE000000, MarkType.Attack8, true); + sa.Method.Mark(0xE000000, MarkType.Bind1, true); + sa.Method.Mark(0xE000000, MarkType.Bind2, true); + sa.Method.Mark(0xE000000, MarkType.Bind3, true); + sa.Method.Mark(0xE000000, MarkType.Stop1, true); + sa.Method.Mark(0xE000000, MarkType.Stop2, true); + sa.Method.Mark(0xE000000, MarkType.Square, true); + sa.Method.Mark(0xE000000, MarkType.Circle, true); + sa.Method.Mark(0xE000000, MarkType.Cross, true); + sa.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + public static void MarkClear(this ScriptAccessory sa, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + sa.Log.Debug($"接收命令:删除标点"); + + if (local) + { + if (localString) + sa.Log.Debug($"[字符模拟] 删除本地标点。"); + else + sa.LocalMarkClear(); + } + else + sa.Method.MarkClear(); + } + + public static void MarkPlayerByIdx(this ScriptAccessory sa, int idx, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{idx}({sa.GetPlayerJobByIndex(idx)})标上{marker}。"); + else + sa.Method.Mark(sa.Data.PartyList[idx], marker, local); + } + + public static void MarkPlayerById(ScriptAccessory sa, uint id, MarkType marker, + bool enable = true, bool local = false, bool localString = false) + { + if (!enable) return; + if (localString) + sa.Log.Debug($"[本地字符模拟] 为{sa.GetPlayerIdIndex(id)}({sa.GetPlayerJobById(id)})标上{marker}。"); + else + sa.Method.Mark(id, marker, local); + } + + public static int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 特殊函数 + +public static class SpecialFunction +{ + public static void SetTargetable(this ScriptAccessory sa, IGameObject? obj, bool targetable) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + if (targetable) + { + if (obj.IsDead || obj.IsTargetable) return; + charaStruct->TargetableStatus |= ObjectTargetableFlags.IsTargetable; + } + else + { + if (!obj.IsTargetable) return; + charaStruct->TargetableStatus &= ~ObjectTargetableFlags.IsTargetable; + } + } + sa.Log.Debug($"SetTargetable {targetable} => {obj.Name} {obj}"); + } + + public static void ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->Scale = scale; + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + sa.Log.Debug($"ScaleModify => {obj.Name.TextValue} | {obj} => {scale}"); + } + + public static void SetRotation(this ScriptAccessory sa, IGameObject? obj, float radian, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(radian); + } + sa.Log.Debug($"改变面向 {obj.Name.TextValue} | {obj.EntityId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.EntityId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(ownerObj, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + } + + public static void SetPosition(this ScriptAccessory sa, IGameObject? obj, Vector3 position, bool show = false) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetPosition(position.X, position.Y, position.Z); + } + sa.Log.Debug($"改变位置 => {obj.Name.TextValue} | {obj.EntityId} => {position}"); + + if (!show) return; + var dp = sa.DrawCircle(position, 0, 2000, $"传送点 {obj.Name.TextValue}", 0.5f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + } +} + +#endregion 特殊函数 + + +public static class NamazuHelper +{ + public class NamazuCommand(ScriptAccessory accessory, string url, string command, string param) + { + private ScriptAccessory accessory { get; set; } = accessory; + private string _url = url; + + public void PostCommand() + { + var url = $"{_url}/{command}"; + accessory.Log.Debug($"向{url}发送{param}"); + accessory.Method.HttpPost(url, param); + } + } + + public class Waymark + { + public ScriptAccessory accessory { get; set; } + private Dictionary _jsonObj = new(); + private string? _jsonPayload; + + public Waymark(ScriptAccessory _accessory) + { + accessory = _accessory; + } + + public void AddWaymarkType(string type, Vector3 pos, bool active = true) + { + string[] validTypes = ["A", "B", "C", "D", "One", "Two", "Three", "Four"]; + var waymarkType = type; + if (!validTypes.Contains(type)) return; + _jsonObj[waymarkType] = new Dictionary + { + { "X", pos.X }, + { "Y", pos.Y }, + { "Z", pos.Z }, + { "Active", active } + }; + } + + public void SetJsonPayload(bool local = true, bool log = true) + { + _jsonObj["LocalOnly"] = local; + _jsonObj["Log"] = log; + _jsonPayload = JsonConvert.SerializeObject(_jsonObj); + } + + public string? GetJsonPayload() + { + if (_jsonPayload == null) + SetJsonPayload(); + return _jsonPayload; + } + + public void PostWaymarkCommand(int port) + { + var param = GetJsonPayload(); + if (param == null) return; + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", param); + post.PostCommand(); + } + + public void ClearWaymarks(int port) + { + var post = new NamazuCommand(accessory, $"http://127.0.0.1:{port}", "place", "clear"); + post.PostCommand(); + } + } +} + +public static class DrawHelper +{ + public static void DrawBeam(ScriptAccessory accessory, Vector3 sourcePosition, Vector3 targetPosition, string name = "Light's Course", int duration = 6700, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = sourcePosition; + dp.TargetPosition = targetPosition; + dp.Scale = new Vector2(10, 50); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawCircle(ScriptAccessory accessory, Vector3 position, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + public static void DrawDisplacement(ScriptAccessory accessory, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = accessory.Data.Me; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawDisplacementby2points(ScriptAccessory accessory, Vector3 origin, Vector3 target, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = origin; + dp.Color = color ?? accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetPosition = target; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void DrawRect(ScriptAccessory accessory, Vector3 position, Vector3 targetPos, Vector2 scale, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.TargetPosition = targetPos; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + public static void DrawFan(ScriptAccessory accessory, Vector3 position, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = position; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawFanOwner(ScriptAccessory accessory, ulong owner, float rotation, Vector2 scale, float angle, int duration, string name, Vector4? color = null, int delay = 0, bool scaleByTime = true, bool fix = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = owner; + dp.Rotation = rotation; + dp.Scale = scale; + dp.Radian = angle * (float.Pi / 180); + dp.Delay = delay; + dp.DestoryAt = duration; + dp.FixRotation = fix; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + public static void DrawLine(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float width, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(width, 1); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + public static void DrawArrow(ScriptAccessory accessory, Vector3 startPosition, Vector3 endPosition, float x, float y, int duration, string name, Vector4? color = null, int delay = 0) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Position = startPosition; + dp.TargetPosition = endPosition; + dp.Scale = new Vector2(x, y); + dp.Delay = delay; + dp.DestoryAt = duration; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Arrow, dp); + } + + public static void DrawCircleObject(ScriptAccessory accessory, ulong? ob, Vector2 scale, int duration, string name, Vector4? color = null, bool scaleByTime = true, int delay = 0) + { + if (ob == null) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = color ?? accessory.Data.DefaultDangerColor; + dp.Owner = ob.Value; + dp.Scale = scale; + dp.Delay = delay; + dp.DestoryAt = duration; + if (scaleByTime) dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } +} +#endregion 函数集 + From d1d93357e4a3dbd184dd8ccb85e72bd63ce7bd19 Mon Sep 17 00:00:00 2001 From: VeeverSW Date: Sun, 13 Sep 2026 19:34:49 +0800 Subject: [PATCH 2/4] feat: delete empty.md for 4.0 repo --- VeeverSW/04-Stormblood/empty.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 VeeverSW/04-Stormblood/empty.md diff --git a/VeeverSW/04-Stormblood/empty.md b/VeeverSW/04-Stormblood/empty.md deleted file mode 100644 index e69de29..0000000 From f663f2223f43bb98f2a61b11c19f0040e1da456f Mon Sep 17 00:00:00 2001 From: VeeverSW Date: Fri, 2 Oct 2026 15:42:17 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20LV.80=20=E4=BC=8A=E7=94=B8=E5=B8=8C?= =?UTF-8?q?=E6=9C=9B=E4=B9=90=E5=9B=AD=20=E5=85=B1=E9=B8=A3=E4=B9=8B?= =?UTF-8?q?=E7=AB=A03=20=20=20=20=20v0.0.0.3=20=20=20=20=201.=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E4=BA=86=E4=BC=A0=E9=80=81=E9=97=A8=E7=9A=84=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E7=BB=98=E5=88=B6=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20=20=20=20=202.=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=85=89?= =?UTF-8?q?=E6=9A=97buff=E4=B8=8D=E7=BB=98=E5=88=B6=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20=20=20=20=203.=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=97=B6=E9=97=B4=E8=BD=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Edens-Verse-Iconoclasm.cs | 236 +++++++++++++----- 1 file changed, 174 insertions(+), 62 deletions(-) diff --git a/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs b/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs index cb30c78..afa9dc0 100644 --- a/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs +++ b/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs @@ -1,42 +1,54 @@ using Dalamud.Game.ClientState.Objects.Types; -using System; -using System.Linq; -using System.Numerics; -using Newtonsoft.Json; using Dalamud.Utility.Numerics; -using KodakkuAssist.Script; -using KodakkuAssist.Module.GameEvent; +using FFXIVClientStructs; +using FFXIVClientStructs.FFXIV.Client.UI; using KodakkuAssist.Module.Draw; using KodakkuAssist.Module.Draw.Manager; -using System.Reflection.Metadata; -using System.Net; -using System.Threading.Tasks; -using System.Runtime.Intrinsics.Arm; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using Lumina.Data.Structs; +using Newtonsoft.Json; +using System; +using System; using System.Collections.Generic; using System.ComponentModel; -using System.Windows; -using FFXIVClientStructs; -using System; +using System.Linq; +using System.Net; +using System.Numerics; +using System.Reflection.Metadata; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.Arm; +using System.Threading.Tasks; +using System.Windows; using System.Xml.Linq; -using FFXIVClientStructs.FFXIV.Client.UI; -using Lumina.Data.Structs; namespace Veever.Shadowbringers.Edens_Verse_Iconoclasm; //^(?!.*((武僧|机工士|龙骑士|学者|舞者|蝰蛇剑士|暗黑骑士|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast|Cancel|Add))).*$ [ScriptType(name: "LV.80 伊甸希望乐园 共鸣之章3", territorys: [904], guid: "7732767d-bfb3-4c96-9719-962ba10cec08", - version: "0.0.0.2", author: "Veever", note: noteStr)] + version: "0.0.0.3", author: "Veever", note: noteStr, updateInfo: UpdateInfo)] public class Edens_Verse_Iconoclasm { const string noteStr = """ - v0.0.0.2: - 1. 此脚本只用了一个arr回放进行测试,如果出现错画的问题,请dc@我并提供arr回放 + v0.0.0.3: + 1. 此脚本只用了2个arr回放进行测试,如果出现错画的问题,请dc@我并提供arr回放 鸭门。 """; + const string UpdateInfo = + $""" + v0.0.0.3 + 1. 修复了传送门的一些绘制错误问题 + 2. 修复了光暗buff不绘制的问题 + 3. 新增支持时间轴 + 鸭门 + ---------------------------------- + + Duckmen. + """; + [UserSetting("文字横幅提示开关")] public bool isText { get; set; } = true; @@ -49,6 +61,104 @@ 1. 此脚本只用了一个arr回放进行测试,如果出现错画的问题 public int LightsCourseCount; public int StackCount; + #region TimeLine + [ScriptTimeline("T1")] + const string T1Timeline = """ + 11.145 info text="StartCasting 虚无波动" duration=0 + 22.343 info text="StartCasting 暗光钉" duration=0 + 37.603 info text="StartCasting 波状号令" duration=0 + 44.807 info text="StartCasting 白光奔流" duration=0 + 47.833 info text="StartCasting 白光奔流" duration=0 + 68.017 info text="StartCasting 白光奔流" duration=0 + 68.017 info text="StartCasting 白光奔流" duration=0 + 71.099 info text="StartCasting 白光奔流" duration=0 + 71.099 info text="StartCasting 白光奔流" duration=0 + 100.217 info text="StartCasting 白光奔流" duration=0 + 100.217 info text="StartCasting 白光奔流" duration=0 + 103.279 info text="StartCasting 白光奔流" duration=0 + 103.279 info text="StartCasting 白光奔流" duration=0 + 124.139 info text="StartCasting 强制传送" duration=0 + 141.253 info text="StartCasting 薄暮机动" duration=0 + 144.315 info text="StartCasting 黑暗之剑" duration=0 + 148.383 info text="StartCasting 白光之锤" duration=0 + 156.395 info text="StartCasting 虚无波动" duration=0 + 163.009 info text="StartCasting 暗光钉" duration=0 + 174.137 info text="StartCasting 属性变动" duration=0 + 184.287 info text="StartCasting 夜袭号令" duration=0 + 191.547 info text="StartCasting 黑暗奔流" duration=0 + 191.547 info text="StartCasting 白光奔流" duration=0 + 203.583 info text="StartCasting 黑暗奔流" duration=0 + 203.583 info text="StartCasting 白光奔流" duration=0 + 210.661 info text="StartCasting 黑暗奔流" duration=0 + 210.661 info text="StartCasting 白光奔流" duration=0 + 236.215 info text="StartCasting 强制传送" duration=2.7 + 243.157 info text="StartCasting 攻击机动" duration=0 + 261.855 info text="StartCasting 白光之矢" duration=0 + 264.993 info text="StartCasting 白光之矢" duration=0 + 278.993 info text="StartCasting 虚无泛滥" duration=6.7 + 296.499 info text="StartCasting 属性变动" duration=0 + 308.079 info text="StartCasting 夜袭号令" duration=0 + 315.867 info text="StartCasting 黑暗奔流" duration=0 + 315.867 info text="StartCasting 白光奔流" duration=0 + """; + + [ScriptTimelineI18n(ScriptTimelineLang.ChineseSimplified)] + static readonly Dictionary T1I18n = new() + { + ["StartCasting 夜袭号令"] = "StartCasting 夜袭号令", + ["StartCasting 属性变动"] = "StartCasting 属性变动", + ["StartCasting 强制传送"] = "StartCasting 强制传送", + ["StartCasting 攻击机动"] = "StartCasting 攻击机动", + ["StartCasting 暗光钉"] = "StartCasting 暗光钉", + ["StartCasting 波状号令"] = "StartCasting 波状号令", + ["StartCasting 白光之矢"] = "StartCasting 白光之矢", + ["StartCasting 白光之锤"] = "StartCasting 白光之锤", + ["StartCasting 白光奔流"] = "StartCasting 白光奔流", + ["StartCasting 薄暮机动"] = "StartCasting 薄暮机动", + ["StartCasting 虚无泛滥"] = "StartCasting 虚无泛滥", + ["StartCasting 虚无波动"] = "StartCasting 虚无波动", + ["StartCasting 黑暗之剑"] = "StartCasting 黑暗之剑", + ["StartCasting 黑暗奔流"] = "StartCasting 黑暗奔流", + }; + + [ScriptTimelineI18n(ScriptTimelineLang.ChineseTraditional)] + static readonly Dictionary T1I18n_ChineseTraditional = new() + { + ["StartCasting 夜袭号令"] = "StartCasting 夜袭号令", + ["StartCasting 属性变动"] = "StartCasting 属性变动", + ["StartCasting 强制传送"] = "StartCasting 强制传送", + ["StartCasting 攻击机动"] = "StartCasting 攻击机动", + ["StartCasting 暗光钉"] = "StartCasting 暗光钉", + ["StartCasting 波状号令"] = "StartCasting 波状号令", + ["StartCasting 白光之矢"] = "StartCasting 白光之矢", + ["StartCasting 白光之锤"] = "StartCasting 白光之锤", + ["StartCasting 白光奔流"] = "StartCasting 白光奔流", + ["StartCasting 薄暮机动"] = "StartCasting 薄暮机动", + ["StartCasting 虚无泛滥"] = "StartCasting 虚无泛滥", + ["StartCasting 虚无波动"] = "StartCasting 虚无波动", + ["StartCasting 黑暗之剑"] = "StartCasting 黑暗之剑", + ["StartCasting 黑暗奔流"] = "StartCasting 黑暗奔流", + }; + + [ScriptTimelineI18n(ScriptTimelineLang.Japanese)] + static readonly Dictionary T1I18n_Japanese = new() + { + ["StartCasting 夜袭号令"] = "StartCasting 夜袭号令", + ["StartCasting 属性变动"] = "StartCasting 属性变动", + ["StartCasting 强制传送"] = "StartCasting 强制传送", + ["StartCasting 攻击机动"] = "StartCasting 攻击机动", + ["StartCasting 暗光钉"] = "StartCasting 暗光钉", + ["StartCasting 波状号令"] = "StartCasting 波状号令", + ["StartCasting 白光之矢"] = "StartCasting 白光之矢", + ["StartCasting 白光之锤"] = "StartCasting 白光之锤", + ["StartCasting 白光奔流"] = "StartCasting 白光奔流", + ["StartCasting 薄暮机动"] = "StartCasting 薄暮机动", + ["StartCasting 虚无泛滥"] = "StartCasting 虚无泛滥", + ["StartCasting 虚无波动"] = "StartCasting 虚无波动", + ["StartCasting 黑暗之剑"] = "StartCasting 黑暗之剑", + ["StartCasting 黑暗奔流"] = "StartCasting 黑暗奔流", + }; + #endregion public List BallCheckList = new List { //From N (left to Right) @@ -88,12 +198,14 @@ 1. 此脚本只用了一个arr回放进行测试,如果出现错画的问题 }; private readonly object LightsCourseLock = new object(); + private int DarkStatus; public void Init(ScriptAccessory accessory) { accessory.Method.RemoveDraw(".*"); LightsCourseCount = 0; StackCount = 0; + DarkStatus = 0; } public void DebugMsg(string str, ScriptAccessory accessory) @@ -103,12 +215,9 @@ public void DebugMsg(string str, ScriptAccessory accessory) } [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] - public void debug(Event @event, ScriptAccessory accessory) - { - var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); - if (myself == null) return; - var buffId = myself.HasStatus(2238) ? 2238 : 2239; - DebugMsg($"buffId: {buffId}", accessory); + public void debug(Event @event, ScriptAccessory accessory) { + DebugMsg($"Dark: {DarkStatus}", accessory); + DebugMsg($"RealLightsCourseCount: {LightsCourseCount}", accessory); } @@ -171,25 +280,27 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); if (@event["ActionId"] == "20067") { - DebugMsg($"{LightsCourseCount}: In 20067", accessory); + DebugMsg($"{LightsCourseCount}: In 4 & 5 20067", accessory); var index0 = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DebugMsg($"{index0}: index", accessory); if (index0 == 1) { - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 4 & 5", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 5500, "1 Light's Course 4 & 5", delay: 6700); } else if (index0 == 2) { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 4 & 5", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "2 Light's Course 4 & 5", delay: 6700); } DebugMsg($"is drawing 20067", accessory); - DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "Light's Course 4 & 5"); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "3 Light's Course 4 & 5"); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 4000, - "Light's Course 0 & 1 SAFE", new Vector4(0, 1, 0, 1), 6800); + "1 Light's Course 4 & 5 SAFE", new Vector4(0, 1, 0, 1), 6800); } else { + DebugMsg($"{LightsCourseCount}: In 4 & 5 else", accessory); var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); - DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 4 & 5"); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "4 Light's Course 4 & 5"); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 4000, - "Light's Course 0 & 1 SAFE", new Vector4(0, 1, 0, 1), 6800); + "2 Light's Course 4 & 5 SAFE", new Vector4(0, 1, 0, 1), 6800); } } @@ -202,10 +313,10 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) var index0 = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); if (index0 == 1) { - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 5500, "Light's Course 6 & 7", delay: 6700); } else if (index0 == 2) { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); } DebugMsg($"is drawing 20067", accessory); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "Light's Course 6 & 7"); @@ -231,27 +342,27 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) { if (LightsCourseCount == 8 || LightsCourseCount == 9) { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "1 - 5:5 Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "1 - 7:7 Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 2000, "1 - 5:5 Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 2000, "1 - 7:7 Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); } else { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "1 - 5:5 Light's Course 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "1 - 7:7 Light's Course 10 & 11", delay: 6700); } } else if (index0 == 2) { if (LightsCourseCount == 8 || LightsCourseCount == 9) { - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); - DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "2 - 4:4 Light's Course 8 & 9", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "2 - 6:6 Light's Course 8 & 9", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 2000, "2 - 4:4 Light's Course 8 & 9 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 2000, "2 - 6:6 Light's Course 8 & 9 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); } else { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "2 - 6:6 Light's Course 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "2 - 4:4 Light's Course 10 & 11", delay: 6700); } } var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); @@ -280,17 +391,15 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) LightsCourseCount == 23 || LightsCourseCount == 28 || LightsCourseCount == 29 || LightsCourseCount == 30 || LightsCourseCount == 31) { - // 2238 Light //19516 & 19490 // 0x8BE - // 2239 Darkness //19517 & 19491 // 0x8BF + // 2238 Light //19516 & 19490 // 0x8BE DarkStatus == 0 + // 2239 Darkness //19517 & 19491 // 0x8BF DarkStatus == 1 DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); - var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); - if (myself == null) return; - var buffId = myself.HasStatus(2238) ? 2238 : 2239; - DebugMsg($"buffId: {buffId}", accessory); + + DebugMsg($"DarkStatus: {DarkStatus}", accessory); if (@event["ActionId"] == "19516" || @event["ActionId"] == "19490") { DebugMsg($"{LightsCourseCount}: In 19516", accessory); - if (buffId == 2238) + if (DarkStatus == 0) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(1, 0, 0, 1)); @@ -302,7 +411,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } else if (@event["ActionId"] == "19517" || @event["ActionId"] == "19491") { DebugMsg($"{LightsCourseCount}: In 19517", accessory); - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(1, 0, 0, 1)); @@ -319,13 +428,11 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) // 2238 Light //19516 & 19490 // 0x8BE // 2239 Darkness //19517 & 19491 & 19521 // 0x8BF DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); - var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); - if (myself == null) return; - var buffId = myself.HasStatus(2238) ? 2238 : 2239; - DebugMsg($"buffId: {buffId}", accessory); + + DebugMsg($"DarkStatus: {DarkStatus}", accessory); if (@event["ActionId"] == "19521") { - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); @@ -354,7 +461,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } } else if (@event["ActionId"] == "19516") { - if (buffId == 2238) + if (DarkStatus == 0) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); @@ -385,7 +492,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) { if (@event["ActionId"] == "19517") { - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); @@ -397,7 +504,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } if (@event["ActionId"] == "19518") { - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1)); @@ -416,7 +523,12 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } } - + [ScriptMethod(name: "偏属性记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(2239|2238)$"], userControl: false)] + public void RecordStatus(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId != accessory.Data.Me) return; + DarkStatus = @event["StatusID"].Equals(2239) ? 1 : 0; + } From d1deb7dea9baf9fa1287897f354ce4a9e97428b6 Mon Sep 17 00:00:00 2001 From: VeeverSW Date: Fri, 2 Oct 2026 15:44:54 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20LV.80=20=E4=BC=8A=E7=94=B8=E5=B8=8C?= =?UTF-8?q?=E6=9C=9B=E4=B9=90=E5=9B=AD=20=E5=85=B1=E9=B8=A3=E4=B9=8B?= =?UTF-8?q?=E7=AB=A03=20=20=20=20=20v0.0.0.3=20=20=20=20=201.=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E4=BA=86=E4=BC=A0=E9=80=81=E9=97=A8=E7=9A=84=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E7=BB=98=E5=88=B6=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20=20=20=20=202.=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=85=89?= =?UTF-8?q?=E6=9A=97buff=E4=B8=8D=E7=BB=98=E5=88=B6=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20=20=20=20=203.=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=97=B6=E9=97=B4=E8=BD=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Edens-Verse-Iconoclasm.cs | 236 +++++++++++++----- 1 file changed, 174 insertions(+), 62 deletions(-) diff --git a/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs b/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs index cb30c78..afa9dc0 100644 --- a/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs +++ b/VeeverSW/05-Shadowbringers/Edens-Verse-Iconoclasm.cs @@ -1,42 +1,54 @@ using Dalamud.Game.ClientState.Objects.Types; -using System; -using System.Linq; -using System.Numerics; -using Newtonsoft.Json; using Dalamud.Utility.Numerics; -using KodakkuAssist.Script; -using KodakkuAssist.Module.GameEvent; +using FFXIVClientStructs; +using FFXIVClientStructs.FFXIV.Client.UI; using KodakkuAssist.Module.Draw; using KodakkuAssist.Module.Draw.Manager; -using System.Reflection.Metadata; -using System.Net; -using System.Threading.Tasks; -using System.Runtime.Intrinsics.Arm; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using Lumina.Data.Structs; +using Newtonsoft.Json; +using System; +using System; using System.Collections.Generic; using System.ComponentModel; -using System.Windows; -using FFXIVClientStructs; -using System; +using System.Linq; +using System.Net; +using System.Numerics; +using System.Reflection.Metadata; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.Arm; +using System.Threading.Tasks; +using System.Windows; using System.Xml.Linq; -using FFXIVClientStructs.FFXIV.Client.UI; -using Lumina.Data.Structs; namespace Veever.Shadowbringers.Edens_Verse_Iconoclasm; //^(?!.*((武僧|机工士|龙骑士|学者|舞者|蝰蛇剑士|暗黑骑士|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast|Cancel|Add))).*$ [ScriptType(name: "LV.80 伊甸希望乐园 共鸣之章3", territorys: [904], guid: "7732767d-bfb3-4c96-9719-962ba10cec08", - version: "0.0.0.2", author: "Veever", note: noteStr)] + version: "0.0.0.3", author: "Veever", note: noteStr, updateInfo: UpdateInfo)] public class Edens_Verse_Iconoclasm { const string noteStr = """ - v0.0.0.2: - 1. 此脚本只用了一个arr回放进行测试,如果出现错画的问题,请dc@我并提供arr回放 + v0.0.0.3: + 1. 此脚本只用了2个arr回放进行测试,如果出现错画的问题,请dc@我并提供arr回放 鸭门。 """; + const string UpdateInfo = + $""" + v0.0.0.3 + 1. 修复了传送门的一些绘制错误问题 + 2. 修复了光暗buff不绘制的问题 + 3. 新增支持时间轴 + 鸭门 + ---------------------------------- + + Duckmen. + """; + [UserSetting("文字横幅提示开关")] public bool isText { get; set; } = true; @@ -49,6 +61,104 @@ 1. 此脚本只用了一个arr回放进行测试,如果出现错画的问题 public int LightsCourseCount; public int StackCount; + #region TimeLine + [ScriptTimeline("T1")] + const string T1Timeline = """ + 11.145 info text="StartCasting 虚无波动" duration=0 + 22.343 info text="StartCasting 暗光钉" duration=0 + 37.603 info text="StartCasting 波状号令" duration=0 + 44.807 info text="StartCasting 白光奔流" duration=0 + 47.833 info text="StartCasting 白光奔流" duration=0 + 68.017 info text="StartCasting 白光奔流" duration=0 + 68.017 info text="StartCasting 白光奔流" duration=0 + 71.099 info text="StartCasting 白光奔流" duration=0 + 71.099 info text="StartCasting 白光奔流" duration=0 + 100.217 info text="StartCasting 白光奔流" duration=0 + 100.217 info text="StartCasting 白光奔流" duration=0 + 103.279 info text="StartCasting 白光奔流" duration=0 + 103.279 info text="StartCasting 白光奔流" duration=0 + 124.139 info text="StartCasting 强制传送" duration=0 + 141.253 info text="StartCasting 薄暮机动" duration=0 + 144.315 info text="StartCasting 黑暗之剑" duration=0 + 148.383 info text="StartCasting 白光之锤" duration=0 + 156.395 info text="StartCasting 虚无波动" duration=0 + 163.009 info text="StartCasting 暗光钉" duration=0 + 174.137 info text="StartCasting 属性变动" duration=0 + 184.287 info text="StartCasting 夜袭号令" duration=0 + 191.547 info text="StartCasting 黑暗奔流" duration=0 + 191.547 info text="StartCasting 白光奔流" duration=0 + 203.583 info text="StartCasting 黑暗奔流" duration=0 + 203.583 info text="StartCasting 白光奔流" duration=0 + 210.661 info text="StartCasting 黑暗奔流" duration=0 + 210.661 info text="StartCasting 白光奔流" duration=0 + 236.215 info text="StartCasting 强制传送" duration=2.7 + 243.157 info text="StartCasting 攻击机动" duration=0 + 261.855 info text="StartCasting 白光之矢" duration=0 + 264.993 info text="StartCasting 白光之矢" duration=0 + 278.993 info text="StartCasting 虚无泛滥" duration=6.7 + 296.499 info text="StartCasting 属性变动" duration=0 + 308.079 info text="StartCasting 夜袭号令" duration=0 + 315.867 info text="StartCasting 黑暗奔流" duration=0 + 315.867 info text="StartCasting 白光奔流" duration=0 + """; + + [ScriptTimelineI18n(ScriptTimelineLang.ChineseSimplified)] + static readonly Dictionary T1I18n = new() + { + ["StartCasting 夜袭号令"] = "StartCasting 夜袭号令", + ["StartCasting 属性变动"] = "StartCasting 属性变动", + ["StartCasting 强制传送"] = "StartCasting 强制传送", + ["StartCasting 攻击机动"] = "StartCasting 攻击机动", + ["StartCasting 暗光钉"] = "StartCasting 暗光钉", + ["StartCasting 波状号令"] = "StartCasting 波状号令", + ["StartCasting 白光之矢"] = "StartCasting 白光之矢", + ["StartCasting 白光之锤"] = "StartCasting 白光之锤", + ["StartCasting 白光奔流"] = "StartCasting 白光奔流", + ["StartCasting 薄暮机动"] = "StartCasting 薄暮机动", + ["StartCasting 虚无泛滥"] = "StartCasting 虚无泛滥", + ["StartCasting 虚无波动"] = "StartCasting 虚无波动", + ["StartCasting 黑暗之剑"] = "StartCasting 黑暗之剑", + ["StartCasting 黑暗奔流"] = "StartCasting 黑暗奔流", + }; + + [ScriptTimelineI18n(ScriptTimelineLang.ChineseTraditional)] + static readonly Dictionary T1I18n_ChineseTraditional = new() + { + ["StartCasting 夜袭号令"] = "StartCasting 夜袭号令", + ["StartCasting 属性变动"] = "StartCasting 属性变动", + ["StartCasting 强制传送"] = "StartCasting 强制传送", + ["StartCasting 攻击机动"] = "StartCasting 攻击机动", + ["StartCasting 暗光钉"] = "StartCasting 暗光钉", + ["StartCasting 波状号令"] = "StartCasting 波状号令", + ["StartCasting 白光之矢"] = "StartCasting 白光之矢", + ["StartCasting 白光之锤"] = "StartCasting 白光之锤", + ["StartCasting 白光奔流"] = "StartCasting 白光奔流", + ["StartCasting 薄暮机动"] = "StartCasting 薄暮机动", + ["StartCasting 虚无泛滥"] = "StartCasting 虚无泛滥", + ["StartCasting 虚无波动"] = "StartCasting 虚无波动", + ["StartCasting 黑暗之剑"] = "StartCasting 黑暗之剑", + ["StartCasting 黑暗奔流"] = "StartCasting 黑暗奔流", + }; + + [ScriptTimelineI18n(ScriptTimelineLang.Japanese)] + static readonly Dictionary T1I18n_Japanese = new() + { + ["StartCasting 夜袭号令"] = "StartCasting 夜袭号令", + ["StartCasting 属性变动"] = "StartCasting 属性变动", + ["StartCasting 强制传送"] = "StartCasting 强制传送", + ["StartCasting 攻击机动"] = "StartCasting 攻击机动", + ["StartCasting 暗光钉"] = "StartCasting 暗光钉", + ["StartCasting 波状号令"] = "StartCasting 波状号令", + ["StartCasting 白光之矢"] = "StartCasting 白光之矢", + ["StartCasting 白光之锤"] = "StartCasting 白光之锤", + ["StartCasting 白光奔流"] = "StartCasting 白光奔流", + ["StartCasting 薄暮机动"] = "StartCasting 薄暮机动", + ["StartCasting 虚无泛滥"] = "StartCasting 虚无泛滥", + ["StartCasting 虚无波动"] = "StartCasting 虚无波动", + ["StartCasting 黑暗之剑"] = "StartCasting 黑暗之剑", + ["StartCasting 黑暗奔流"] = "StartCasting 黑暗奔流", + }; + #endregion public List BallCheckList = new List { //From N (left to Right) @@ -88,12 +198,14 @@ 1. 此脚本只用了一个arr回放进行测试,如果出现错画的问题 }; private readonly object LightsCourseLock = new object(); + private int DarkStatus; public void Init(ScriptAccessory accessory) { accessory.Method.RemoveDraw(".*"); LightsCourseCount = 0; StackCount = 0; + DarkStatus = 0; } public void DebugMsg(string str, ScriptAccessory accessory) @@ -103,12 +215,9 @@ public void DebugMsg(string str, ScriptAccessory accessory) } [ScriptMethod(name: "debug", eventType: EventTypeEnum.Chat, eventCondition: ["Message:debug"])] - public void debug(Event @event, ScriptAccessory accessory) - { - var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); - if (myself == null) return; - var buffId = myself.HasStatus(2238) ? 2238 : 2239; - DebugMsg($"buffId: {buffId}", accessory); + public void debug(Event @event, ScriptAccessory accessory) { + DebugMsg($"Dark: {DarkStatus}", accessory); + DebugMsg($"RealLightsCourseCount: {LightsCourseCount}", accessory); } @@ -171,25 +280,27 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); if (@event["ActionId"] == "20067") { - DebugMsg($"{LightsCourseCount}: In 20067", accessory); + DebugMsg($"{LightsCourseCount}: In 4 & 5 20067", accessory); var index0 = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); + DebugMsg($"{index0}: index", accessory); if (index0 == 1) { - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 4 & 5", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 5500, "1 Light's Course 4 & 5", delay: 6700); } else if (index0 == 2) { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 4 & 5", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "2 Light's Course 4 & 5", delay: 6700); } DebugMsg($"is drawing 20067", accessory); - DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "Light's Course 4 & 5"); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "3 Light's Course 4 & 5"); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 4000, - "Light's Course 0 & 1 SAFE", new Vector4(0, 1, 0, 1), 6800); + "1 Light's Course 4 & 5 SAFE", new Vector4(0, 1, 0, 1), 6800); } else { + DebugMsg($"{LightsCourseCount}: In 4 & 5 else", accessory); var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); - DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 4 & 5"); + DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "4 Light's Course 4 & 5"); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 4000, - "Light's Course 0 & 1 SAFE", new Vector4(0, 1, 0, 1), 6800); + "2 Light's Course 4 & 5 SAFE", new Vector4(0, 1, 0, 1), 6800); } } @@ -202,10 +313,10 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) var index0 = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); if (index0 == 1) { - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 5500, "Light's Course 6 & 7", delay: 6700); } else if (index0 == 2) { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 6 & 7", delay: 6700); } DebugMsg($"is drawing 20067", accessory); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index0], new Vector2(10, 25), 6700, "Light's Course 6 & 7"); @@ -231,27 +342,27 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) { if (LightsCourseCount == 8 || LightsCourseCount == 9) { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "1 - 5:5 Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "1 - 7:7 Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 2000, "1 - 5:5 Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 2000, "1 - 7:7 Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); } else { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "1 - 5:5 Light's Course 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "1 - 7:7 Light's Course 10 & 11", delay: 6700); } } else if (index0 == 2) { if (LightsCourseCount == 8 || LightsCourseCount == 9) { - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", new Vector4(1, 0, 0, 1), delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); - DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 2000, "Light's Course 8 & 9 & 10 & 11 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "2 - 4:4 Light's Course 8 & 9", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "2 - 6:6 Light's Course 8 & 9", new Vector4(1, 0, 0, 1), delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 2000, "2 - 4:4 Light's Course 8 & 9 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 2000, "2 - 6:6 Light's Course 8 & 9 SAFE", new Vector4(0, 1, 0, 1), delay: 13400); } else { - DrawHelper.DrawRect(accessory, BallStartList[5], centerPoint[5], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); - DrawHelper.DrawRect(accessory, BallStartList[7], centerPoint[7], new Vector2(10, 50), 6700, "Light's Course 8 & 9 & 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[6], centerPoint[6], new Vector2(10, 50), 6700, "2 - 6:6 Light's Course 10 & 11", delay: 6700); + DrawHelper.DrawRect(accessory, BallStartList[4], centerPoint[4], new Vector2(10, 50), 6700, "2 - 4:4 Light's Course 10 & 11", delay: 6700); } } var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); @@ -280,17 +391,15 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) LightsCourseCount == 23 || LightsCourseCount == 28 || LightsCourseCount == 29 || LightsCourseCount == 30 || LightsCourseCount == 31) { - // 2238 Light //19516 & 19490 // 0x8BE - // 2239 Darkness //19517 & 19491 // 0x8BF + // 2238 Light //19516 & 19490 // 0x8BE DarkStatus == 0 + // 2239 Darkness //19517 & 19491 // 0x8BF DarkStatus == 1 DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); - var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); - if (myself == null) return; - var buffId = myself.HasStatus(2238) ? 2238 : 2239; - DebugMsg($"buffId: {buffId}", accessory); + + DebugMsg($"DarkStatus: {DarkStatus}", accessory); if (@event["ActionId"] == "19516" || @event["ActionId"] == "19490") { DebugMsg($"{LightsCourseCount}: In 19516", accessory); - if (buffId == 2238) + if (DarkStatus == 0) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(1, 0, 0, 1)); @@ -302,7 +411,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } else if (@event["ActionId"] == "19517" || @event["ActionId"] == "19491") { DebugMsg($"{LightsCourseCount}: In 19517", accessory); - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 5000, "Light's Course 12|3456789", new Vector4(1, 0, 0, 1)); @@ -319,13 +428,11 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) // 2238 Light //19516 & 19490 // 0x8BE // 2239 Darkness //19517 & 19491 & 19521 // 0x8BF DebugMsg($"{LightsCourseCount}: {@event.ActionId()}", accessory); - var myself = IbcHelper.GetByEntityId(accessory, accessory.Data.Me); - if (myself == null) return; - var buffId = myself.HasStatus(2238) ? 2238 : 2239; - DebugMsg($"buffId: {buffId}", accessory); + + DebugMsg($"DarkStatus: {DarkStatus}", accessory); if (@event["ActionId"] == "19521") { - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); @@ -354,7 +461,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } } else if (@event["ActionId"] == "19516") { - if (buffId == 2238) + if (DarkStatus == 0) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); @@ -385,7 +492,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) { if (@event["ActionId"] == "19517") { - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(1, 0, 0, 1)); @@ -397,7 +504,7 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } if (@event["ActionId"] == "19518") { - if (buffId == 2239) + if (DarkStatus == 1) { var index = FindExactPositionIndex(@event.SourcePosition(), BallCheckList); DrawHelper.DrawRect(accessory, @event.SourcePosition(), centerPoint[index], new Vector2(10, 50), 6700, "Light's Course 24, 25, 26, 27", new Vector4(0, 1, 0, 1)); @@ -416,7 +523,12 @@ public void LightsAndDarknessCourse(Event @event, ScriptAccessory accessory) } } - + [ScriptMethod(name: "偏属性记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(2239|2238)$"], userControl: false)] + public void RecordStatus(Event @event, ScriptAccessory accessory) + { + if (@event.TargetId != accessory.Data.Me) return; + DarkStatus = @event["StatusID"].Equals(2239) ? 1 : 0; + }