diff --git a/lianying1997/04-StormBlood/UCOB.cs b/lianying1997/04-StormBlood/UCOB.cs new file mode 100644 index 0000000..5684417 --- /dev/null +++ b/lianying1997/04-StormBlood/UCOB.cs @@ -0,0 +1,2218 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Extensions; +using Dalamud.Utility.Numerics; +using System.Numerics; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; +using KodakkuAssist.Module.GameOperate; +using System.ComponentModel; +using System.Collections; +using KodakkuAssist.Module.Draw.Manager; + +namespace UsamisScript.StormBlood.Ucob; + +[ScriptType(name: "UCOB [巴哈姆特绝境战]", territorys: [733], guid: "884e415a-1210-44cc-bdff-8fab6878e87d", + version: "0.0.2.3", author: "Joshua and Usami", note: noteStr, updateInfo: UpdateInfo)] +public class Ucob +{ + // TODO + // 暂无,硬要说的话,添加双塔垂直下落范围 + + const string noteStr = + """ + 基于@Joshua的巴哈初版程序改写扩充, + 感谢合作者@KnightRider,@Meva。 + 请先按需求检查并设置“用户设置”栏目。 + 鸭门。 + """; + + private const string UpdateInfo = + """ + 1. 现在若引导俯冲的不是自己,将不会再进行指路 + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public bool DebugMode { get; set; } = false; + + [UserSetting("黑球行动轨迹长度")] + public float blackOrbTrackLength { get; set; } = 4; + + [UserSetting("黑球行动方向绘图颜色")] + public ScriptColor blackOrbTrackColor { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + + [UserSetting("是否绘出黑球爆炸范围")] + public bool showBlackOrbField { get; set; } = true; + + [UserSetting("黑球爆炸范围绘图颜色")] + public ScriptColor blackOrbFieldColor { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + + [UserSetting("P2:是否展示其他玩家的小龙俯冲引导路径")] + public bool showOtherCauterizeRoute { get; set; } = false; + + [UserSetting("P3:【连击】是否提示全局撞球/截球信息")] + public bool showGlobalTenStrikeBlackOrbMsg { get; set; } = true; + + public enum BahamutFavorNorthTypeEnum + { + 画出12点_ShowTempNorth, + 画出8条分散方向_ShowRecomDir, + 不画_DontDraw, + } + + [UserSetting("P4:场中分摊/分散+旋风时以何种形式根据时机画出指向标记")] + public BahamutFavorNorthTypeEnum BahamutFavorNorth { get; set; } = BahamutFavorNorthTypeEnum.画出8条分散方向_ShowRecomDir; + + [UserSetting("P5:地火爆炸区颜色")] + public ScriptColor exflareColor { get; set; } = new ScriptColor { V4 = new Vector4(0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("P5:地火预警区颜色")] + public ScriptColor exflareWarnColor { get; set; } = new ScriptColor { V4 = new Vector4(0f, 0.5f, 1.0f, 1.0f) }; + + [UserSetting("P5:是否提示分摊/死刑次数")] + public bool showStackBusterNum { get; set; } = false; + + [UserSetting("站位提示圈绘图-普通颜色")] + public ScriptColor posColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public ScriptColor posColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.2f, 1.0f, 0.2f, 1.0f) }; + + public ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public ScriptColor colorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 1f, 1.0f) }; + + public enum UCOB_Phase + { + Twintania, // P1 + Nael, // P2 + Quickmarch_1st, + Blackfire_2nd, + Fellruin_3rd, + Heavensfall_4th, + Tenstrike_5th, + GrandOctet_6th, + BahamutFavor, // P4 + FlamesRebirth, // P5 + } + UCOB_Phase phase = UCOB_Phase.Twintania; + int restrictorNum = 0; // P1拘束器掉落次数 + Vector3[] RestrictorPos = new Vector3[3]; // P1拘束器位置记录 + List GenerateTarget = [false, false, false, false, false, false, false, false]; // 被点名魔力炼成 + List DeathSentenceTarget = [0, 0, 0]; // P2死宣目标 + List FireBallStatus = [0, 0, 0, 0, 0, 0, 0, 0]; // P2火buff状态 + int FireBallTimes = 0; // P2火球分摊次数 + Dictionary CauterizeDragons = new(); // P2小龙字典(id, 位置) + int CauterizeTimes = 0; // P2小龙引导次数 + Vector3 QuickMarchPos = new(0, 0, 0); // P3进军位置 + bool QuickMarchStackDrawn = false; // P3进军核爆绘图完成记录 + bool QuickMarchEarthShakerDrawn = false; // P3进军大地摇动绘图完成记录 + Vector3 NaelPosition = new(0, 0, 0); // P3奈尔位置记录 + Vector3 TwintaniaPosition = new(0, 0, 0); // P3双塔位置记录 + Vector3 BahamutPosition = new(0, 0, 0); // P3巴哈位置记录 + bool blackFireDrawn = false; // P3黑炎指路绘图完成记录 + List HeavensFallDangerPos = [false, false, false, false, false, false, false, false]; // P3天地安全位置(仅判断左右) + List HeavensFallBossPos = [false, false, false, false, false, false, false, false]; // P3天地BOSS所在位置(仅判断左右) + List HeavensFallTowerPos = [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]; // P4天地塔位置 + bool HeavensFallTowerDrawn = false; // P3天地塔目标绘图完成记录 + bool isTenStrikeTarget = false; // P3连击玩家是黑球点名目标 + bool TenStrikeBlackOrbDrawn = false; // P3连击黑球指路绘图完成记录 + int TenStrikeEarthShakerNum = 0; // P3连击大地摇动点名次数 + bool isEarthShakerFirstRound = false; // P3连击大地摇动是否为第一轮 + bool grandOctDrawn = false; // P3群龙起始位置绘图完成记录 + int grandOctIconNum = 0; // P3群龙点名次数 + List grandOctTargetChosen = [false, false, false, false, false, false, false, false]; // P3群龙目标选择 + Vector3 BahamutFavorPos = new(0, 0, 0); // P4以初始拉怪点为12点(右下) + int ArkMornNum = 0; // P5死亡轮回死刑次数 + int MornAfahNum = 0; // P5无尽顿悟分摊次数 + private static readonly Dictionary<(int, int, int, int, int), (int, int, int)> CauterizeSafePos = + new Dictionary<(int, int, int, int, int), (int, int, int)> + { + {(1, 2, 3, 4, 5), (11, 5, 7)}, + {(1, 2, 3, 4, 6), (11, 5, 7)}, + {(1, 2, 3, 4, 7), (11, 5, 7)}, + {(1, 2, 3, 4, 8), (11, 5, 8)}, + {(1, 2, 3, 5, 6), (2, 5, 8)}, + {(1, 2, 3, 5, 7), (11, 5, 8)}, + {(1, 2, 3, 5, 8), (2, 5, 8)}, + {(1, 2, 3, 6, 7), (2, 5, 10)}, + {(1, 2, 3, 6, 8), (11, 5, 8)}, + {(1, 2, 3, 7, 8), (2, 5, 8)}, + {(1, 2, 4, 5, 6), (2, 6, 9)}, + {(1, 2, 4, 5, 7), (11, 3, 8)}, + {(1, 2, 4, 5, 8), (2, 6, 8)}, + {(1, 2, 4, 6, 7), (2, 6, 10)}, + {(1, 2, 4, 6, 8), (2, 6, 9)}, + {(1, 2, 4, 7, 8), (11, 3, 8)}, + {(1, 2, 5, 6, 7), (2, 5, 10)}, + {(1, 2, 5, 6, 8), (2, 5, 10)}, + {(1, 2, 5, 7, 8), (11, 5, 8)}, + {(1, 2, 6, 7, 8), (2, 6, 11)}, + {(1, 3, 4, 5, 6), (2, 6, 8)}, + {(1, 3, 4, 5, 7), (2, 6, 8)}, + {(1, 3, 4, 5, 8), (2, 6, 8)}, + {(1, 3, 4, 6, 7), (2, 6, 10)}, + {(1, 3, 4, 6, 8), (2, 6, 9)}, + {(1, 3, 4, 7, 8), (2, 6, 8)}, + {(1, 3, 5, 6, 7), (2, 5, 10)}, + {(1, 3, 5, 6, 8), (2, 5, 9)}, + {(1, 3, 5, 7, 8), (2, 5, 8)}, + {(1, 3, 6, 7, 8), (2, 6, 11)}, + {(1, 4, 5, 6, 7), (2, 8, 10)}, + {(1, 4, 5, 6, 8), (2, 8, 10)}, + {(1, 4, 5, 7, 8), (2, 8, 11)}, + {(1, 4, 6, 7, 8), (2, 6, 11)}, + {(1, 5, 6, 7, 8), (3, 9, 11)}, + {(2, 3, 4, 5, 6), (1, 6, 8)}, + {(2, 3, 4, 5, 7), (1, 6, 8)}, + {(2, 3, 4, 5, 8), (1, 6, 9)}, + {(2, 3, 4, 6, 7), (4, 7, 10)}, + {(2, 3, 4, 6, 8), (1, 6, 9)}, + {(2, 3, 4, 7, 8), (4, 7, 11)}, + {(2, 3, 5, 6, 7), (4, 8, 10)}, + {(2, 3, 5, 6, 8), (1, 5, 9)}, + {(2, 3, 5, 7, 8), (4, 8, 11)}, + {(2, 3, 6, 7, 8), (11, 6, 11)}, + {(2, 4, 5, 6, 7), (3, 8, 10)}, + {(2, 4, 5, 6, 8), (3, 8, 10)}, + {(2, 4, 5, 7, 8), (3, 8, 11)}, + {(2, 4, 6, 7, 8), (3, 9, 11)}, + {(2, 5, 6, 7, 8), (4, 9, 11)}, + {(3, 4, 5, 6, 7), (2, 5, 10)}, + {(3, 4, 5, 6, 8), (2, 7, 10)}, + {(3, 4, 5, 7, 8), (2, 8, 11)}, + {(3, 4, 6, 7, 8), (5, 9, 11)}, + {(3, 5, 6, 7, 8), (5, 9, 11)}, + {(4, 5, 6, 7, 8), (4, 9, 11)} + }; + + public void Init(ScriptAccessory accessory) + { + phase = UCOB_Phase.Twintania; + restrictorNum = 0; // P1拘束器掉落次数 + + RestrictorPos[0] = new(0, 0, 0); // P1拘束器位置记录 + RestrictorPos[1] = new(0, 0, 0); + RestrictorPos[2] = new(0, 0, 0); + + GenerateTarget = [false, false, false, false, false, false, false, false]; // 被点名魔力炼成 + + DeathSentenceTarget = [0, 0, 0]; // P2死宣目标 + FireBallStatus = [0, 0, 0, 0, 0, 0, 0, 0]; // P2火buff状态 + FireBallTimes = 0; // P2火球分摊次数 + CauterizeDragons = new(); // P2小龙字典(id, 位置) + CauterizeTimes = 0; // P2小龙引导次数 + + QuickMarchPos = new(0, 0, 0); // P3进军位置 + QuickMarchStackDrawn = false; // P3进军核爆绘图完成记录 + QuickMarchEarthShakerDrawn = false; // P3进军大地摇动绘图完成记录 + + blackFireDrawn = false; // P3黑炎指路绘图完成记录 + + NaelPosition = new(0, 0, 0); // P3奈尔位置记录 + TwintaniaPosition = new(0, 0, 0); // P3双塔位置记录 + BahamutPosition = new(0, 0, 0); // P3巴哈位置记录 + + HeavensFallDangerPos = [false, false, false, false, false, false, false, false]; // P3天地安全位置(仅判断左右) + HeavensFallBossPos = [false, false, false, false, false, false, false, false]; // P3天地BOSS所在位置(仅判断左右) + HeavensFallTowerPos = [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]; // P3天地塔 + HeavensFallTowerDrawn = false; // P3天地塔目标绘图完成记录 + + isTenStrikeTarget = false; // P3连击玩家是黑球点名目标 + TenStrikeBlackOrbDrawn = false; // P3连击黑球指路绘图完成记录 + TenStrikeEarthShakerNum = 0; // P3连击大地摇动点名次数 + isEarthShakerFirstRound = false; // P3连击大地摇动是否为第一轮 + + grandOctDrawn = false; // P3群龙起始位置绘图完成记录 + grandOctIconNum = 0; // P3群龙点名次数 + grandOctTargetChosen = [false, false, false, false, false, false, false, false]; // P3群龙目标选择 + + BahamutFavorPos = new(0, 0, 0); // P4以初始拉怪点为12点(右下) + + ArkMornNum = 0; + MornAfahNum = 0; + + accessory.Method.MarkClear(); + accessory.Method.RemoveDraw(".*"); + + } + + private int PositionTo8Dir(Vector3 point, Vector3 centre) + { + var r = Math.Round(4 - 4 * Math.Atan2(point.X - centre.X, point.Z - centre.Z) / Math.PI) % 8; + return (int)r; + } + + private int PositionTo16Dir(Vector3 point, Vector3 centre) + { + var r = Math.Round(8 - 8 * Math.Atan2(point.X - centre.X, point.Z - centre.Z) / Math.PI) % 16; + return (int)r; + } + + private Vector3 RotatePoint(Vector3 point, Vector3 centre, float radian) + { + + Vector2 v2 = new(point.X - centre.X, point.Z - centre.Z); + + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new(centre.X + MathF.Sin(rot) * length, centre.Y, centre.Z - MathF.Cos(rot) * length); + } + + private Vector3 ExtendPoint(Vector3 centre, float radian, float length) + { + return new(centre.X + MathF.Sin(radian) * length, centre.Y, centre.Z - MathF.Cos(radian) * length); + } + + private float FindRadian(Vector3 centre, Vector3 new_point) + { + float radian = MathF.PI - MathF.Atan2(new_point.X - centre.X, new_point.Z - centre.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + private float angle2Rad(float angle) + { + float radian = (float)(angle * Math.PI / 180); + return radian; + } + private int getPlayerIdIndex(ScriptAccessory accessory, uint pid) + { + return accessory.Data.PartyList.IndexOf(pid); + } + + private int getMyIndex(ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + private string getPlayerJobIndex(ScriptAccessory accessory, uint pid) + { + var a = accessory.Data.PartyList.IndexOf(pid); + switch (a) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } + + 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; + } + } + + [ScriptMethod(name: "测试用", eventType: EventTypeEnum.Chat, eventCondition: ["abcdefgh"], userControl: false)] + public void EchoDebug(Event ev, ScriptAccessory sa) + { + } + + #region 全局 + + [ScriptMethod(name: "【全局】黑球路径", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:8160"])] + public void BlackOrbTrack(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"黑球路径-{sid}"; + dp.Scale = new(2f, blackOrbTrackLength); + dp.Color = blackOrbTrackColor.V4.WithW(3); + dp.Owner = sid; + dp.Delay = 3500; + dp.DestoryAt = 10000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "【全局】超新星危险位置", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2003393", "Operate:Add"])] + public void HypernovaField(Event @event, ScriptAccessory accessory) + { + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"超新星危险区"; + dp.Scale = new(5f); + dp.Position = spos; + dp.DestoryAt = 15000; + dp.Color = colorRed.V4.WithW(3); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "【全局】黑球路径删除(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9903"], userControl: false)] + public void BlackOrbTrackRemove(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + accessory.Method.RemoveDraw($"黑球路径-{sid}"); + } + + [ScriptMethod(name: "【全局】黑球点名记录(不可控)", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0076"], userControl: false)] + public void GenerateTargetRecord(Event @event, ScriptAccessory accessory) + { + Task.Delay(50).ContinueWith(t => + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + var tidx = getPlayerIdIndex(accessory, tid); + GenerateTarget[tidx] = true; + if (DebugMode) + { + var tidjob = getPlayerJobIndex(accessory, tid); + accessory.Method.SendChat($"/e 检测到 {tidjob} 被点名魔力炼成。"); + } + + if (phase != UCOB_Phase.Tenstrike_5th) return; + if (tidx == getMyIndex(accessory)) + isTenStrikeTarget = true; + }); + } + + [ScriptMethod(name: "【全局】拘束器位置记录(不可控)", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2001151", "Operate:Add"], userControl: false)] + public void RestrictorPosRecord(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Twintania) return; + var tpos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + + if (DebugMode) + { + accessory.Method.SendChat($"/e 记录到拘束器,位置为({tpos.X}, {tpos.Z})。"); + } + RestrictorPos[restrictorNum] = tpos; + restrictorNum++; + + if (restrictorNum != 3) return; + float restrictorRad1 = FindRadian(new(0, 0, 0), RestrictorPos[1]); + float restrictorRad2 = FindRadian(new(0, 0, 0), RestrictorPos[2]); + float bahamutFavorRad = (restrictorRad1 + restrictorRad2) / 2; + BahamutFavorPos = ExtendPoint(new(0, 0, 0), bahamutFavorRad, 24); + + if (restrictorNum == 3 && DebugMode) + { + accessory.Method.SendChat($"/e 拘束器数量到3,位置记录完毕。"); + } + } + + [ScriptMethod(name: "【全局】刷新黑球点名目标(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"], userControl: false)] + public void RefreshGenerateTarget(Event @event, ScriptAccessory accessory) + { + Task.Delay(500).ContinueWith(t => + { + GenerateTarget = [false, false, false, false, false, false, false, false]; + }); + } + + [ScriptMethod(name: "【全局】黑球爆炸范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"])] + public void BlackOrbField(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + if (!showBlackOrbField) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + for (int i = 0; i < restrictorNum; i++) + { + dp.Name = $"黑球爆炸范围-{i}"; + dp.Scale = new(7.5f); + dp.Color = blackOrbFieldColor.V4.WithW(0.4f); + dp.Position = RestrictorPos[i]; + dp.Delay = 3500; + dp.DestoryAt = phase == UCOB_Phase.Tenstrike_5th ? 3500 : 10000; // 连击的两轮绘图时延作特殊处理 + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "【全局】黑球爆炸范围删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9903"], userControl: false)] + public void BlackOrbFieldRemove(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + // 连击的两轮绘图不删除 + if (phase == UCOB_Phase.Tenstrike_5th) return; + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + int minIdx = getNearestOrbField(spos); + var tidx = getPlayerIdIndex(accessory, tid); + accessory.Method.RemoveDraw($"黑球爆炸范围-{minIdx}"); + accessory.Method.RemoveDraw($"拘束器位置定位{minIdx}"); + accessory.Method.RemoveDraw($"拘束器撞球玩家{tidx}指路"); + } + + [ScriptMethod(name: "【全局】旋风危险位置", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2001168", "Operate:Add"])] + public void Twister_Field(Event @event, ScriptAccessory accessory) + { + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"旋风危险区"; + dp.Scale = new(1.5f); + dp.Position = spos; + dp.DestoryAt = 7000; + dp.Color = colorRed.V4.WithW(3); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private int getNearestOrbField(Vector3 spos) + { + int minIdx = 0; + float minLength = 999f; + for (int i = 0; i < restrictorNum; i++) + { + float length = new Vector2(spos.X - RestrictorPos[i].X, spos.Z - RestrictorPos[i].Z).Length(); + if (length < minLength) + { + minLength = length; + minIdx = i; + } + } + return minIdx; + } + + #endregion + + #region P1:双塔 + + private void drawTwister(int delay, int destoryAt, ScriptAccessory accessory) + { + accessory.Method.TextInfo("旋风", delay, true); + for (var i = 0; i < 8; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"旋风{i}"; + dp.Scale = new(1.5f); + dp.Owner = accessory.Data.PartyList[i]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor.WithW(2); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "P1&P4双塔:旋风自身位置预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9898"])] + public void Twister_PlayerPosition(Event @event, ScriptAccessory accessory) + { + drawTwister(0, 2000, accessory); + } + + [ScriptMethod(name: "P1&P3双塔:双塔火球分摊范围预警", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0075"])] + public void FireBallStackTarget(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"双塔火球分摊范围"; + dp.Scale = new(4f); + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = tid; + dp.DestoryAt = 10000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "P1&P3双塔:双塔火球分摊范围预警删除(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9900", "TargetIndex:1"], userControl: false)] + public void FireBallStackTargetRemove(Event @event, ScriptAccessory accessory) + { + // TMD火球时间还不一样 + accessory.Method.RemoveDraw($"双塔火球分摊范围"); + } + + #endregion + + #region P2:奈尔 + + [ScriptMethod(name: "P2奈尔:阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9922"], userControl: false)] + public void P2_PhaseChange(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Twintania) return; + phase = UCOB_Phase.Nael; + } + + [ScriptMethod(name: "P2奈尔:火龙连线分摊范围预警", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0005"])] + public void FireBallDragonStackTarget(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + FireBallTimes++; + // accessory.Log.Debug($"{accessory.Data.PartyList.Count}"); + var MyIndex = getMyIndex(accessory); + // accessory.Log.Debug($"MyIndex: {MyIndex} "); + // accessory.Log.Debug($"me: {accessory.Data.Me}"); + // accessory.Log.Debug($"party: {accessory.Data.PartyList[0]}, {accessory.Data.PartyList[1]}, {accessory.Data.PartyList[2]}, {accessory.Data.PartyList[3]}, " + + // $"{accessory.Data.PartyList[4]}, {accessory.Data.PartyList[5]}, {accessory.Data.PartyList[6]}, {accessory.Data.PartyList[7]}"); + // accessory.Log.Debug($"FireBallStatus: {BuildListStr(accessory, FireBallStatus)}"); + // accessory.Log.Debug($"FireBallStatus[MyIndex]: {FireBallStatus[MyIndex]}"); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"火龙火球分摊范围"; + dp.Scale = new(4f); + dp.Owner = tid; + dp.DestoryAt = 10000; + switch (FireBallTimes) + { + case 1: + // 去吃1火 + dp.Color = FireBallStatus[MyIndex] != 1 ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + break; + case 2: + // 如果目标是我 或 我有冰状态,去吃2火 + dp.Color = (tid == accessory.Data.Me) | (FireBallStatus[MyIndex] == -1) ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + break; + case 3: + case 4: + // 如果目标是我 或 我无火状态,去吃3/4火 + dp.Color = (tid == accessory.Data.Me) | (FireBallStatus[MyIndex] != 1) ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + break; + default: + return; + } + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(ScriptAccessory accessory, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => item?.ToString() ?? "")); + } + + [ScriptMethod(name: "P2奈尔:火龙连线分摊范围预警删除(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9925", "TargetIndex:1"], userControl: false)] + public void FireBallDragonStackTargetRemove(Event @event, ScriptAccessory accessory) + { + // TMD火线时间还不一样 + accessory.Method.RemoveDraw($"火龙火球分摊范围"); + } + [ScriptMethod(name: "P2奈尔:冰火状态记录添加", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(465|464)$"], userControl: false)] + public void FireBallStatusAddRecord(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Nael) return; + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + var stid = JsonConvert.DeserializeObject(@event["StatusID"]); + var idx = getPlayerIdIndex(accessory, tid); + if (stid == 465) + // -1为冰,+1为火 + FireBallStatus[idx] = -1; + else + FireBallStatus[idx] = 1; + } + [ScriptMethod(name: "P2奈尔:冰火状态记录移除", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:regex:^(465|464)$"], userControl: false)] + public void FireBallStatusRemoveRecord(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Nael) return; + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + var idx = getPlayerIdIndex(accessory, tid); + FireBallStatus[idx] = 0; + } + + [ScriptMethod(name: "P2奈尔:死亡宣告记录(不可控)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:210"], userControl: false)] + public void DeathSentence_Record(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + if (!int.TryParse(@event["DurationMilliseconds"], out var dur)) return; + // DeathSentenceNum++; + switch (dur) + { + case 6000: + DeathSentenceTarget[0] = tid; + if (DebugMode) + accessory.Method.SendChat($"/e 检测到死1玩家:{getPlayerJobIndex(accessory, tid)}"); + break; + case 10000: + DeathSentenceTarget[1] = tid; + if (DebugMode) + accessory.Method.SendChat($"/e 检测到死2玩家:{getPlayerJobIndex(accessory, tid)}"); + break; + case 16000: + DeathSentenceTarget[2] = tid; + if (DebugMode) + accessory.Method.SendChat($"/e 检测到死3玩家:{getPlayerJobIndex(accessory, tid)}"); + break; + } + } + + [ScriptMethod(name: "P2奈尔:救世之翼预警与指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9930"])] + public void WingsOfSalvation_Position(Event @event, ScriptAccessory accessory) + { + Task.Delay(2000).ContinueWith(t => + { + var epos = JsonConvert.DeserializeObject(@event["EffectPosition"]); + uint DoomPlayerID = 0; // DeathSentenceTarget中第一个不为0的值 + + for (int i = 0; i < DeathSentenceTarget.Count; i++) + { + if (DeathSentenceTarget[i] != 0) + { + DoomPlayerID = DeathSentenceTarget[i]; // 赋值给DoomPlayerID + DeathSentenceTarget[i] = 0; // 将该值设为0 + if (DebugMode) + accessory.Method.SendChat($"/e 准备绘制死宣玩家:{getPlayerJobIndex(accessory, DoomPlayerID)}"); + break; // 找到第一个非0值后跳出循环 + } + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"白圈范围预警"; + dp.Scale = new(1.5f); + dp.Position = epos; + dp.Delay = 1000; + dp.DestoryAt = 4000; + dp.Color = DoomPlayerID == accessory.Data.Me ? accessory.Data.DefaultSafeColor.WithW(3) : colorRed.V4.WithW(3); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (DoomPlayerID == accessory.Data.Me) + { + accessory.Method.TextInfo("即将踩白圈", 2000, true); + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Scale = new(0.5f); + dp.Name = $"白圈指路"; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = epos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 1000; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + }); + } + + [ScriptMethod(name: "P2奈尔:雷点名范围预警", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9927"])] + public void ChainLightening(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"雷光链{tid}"; + dp.Scale = new(5); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = tid; + dp.Delay = 4000; + dp.DestoryAt = 2000; + dp.Color = colorPink.V4.WithW(3); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion + + #region P2:奈尔台词 + + private void drawNaelQuote_Circle(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "钢铁"; + dp.Scale = new(8.55f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void drawNaelQuote_Donut(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "月环"; + dp.Scale = new(22); + dp.InnerScale = new(6); + dp.Radian = float.Pi * 2; + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + + private void drawNaelQuote_Stack(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + for (var i = 0; i < 8; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"热离子光束{i}"; + dp.Scale = new(4); + dp.Owner = accessory.Data.PartyList[i]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + private void drawNaelQuote_Spread(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + for (var i = 0; i < 8; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"凶鸟冲{i}"; + dp.Scale = new(3); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = accessory.Data.PartyList[i]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + private void drawNaelQuote_Tank(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"月华冲"; + dp.Scale = new(5); + dp.Owner = sid; + dp.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + dp.CentreOrderIndex = 1; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void drawMeteorStream(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + for (var i = 0; i < 8; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"陨石流{i}"; + dp.Scale = new(4); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = accessory.Data.PartyList[i]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "P2奈尔:台词", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(649[234567]|650[01])$"])] + public void NaelQuotesP2(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var quoteId = @event["Id"]; + + switch (quoteId) + { + case "6492": + { + // 月光啊!照亮铁血霸道! + drawNaelQuote_Donut(sid, 0, 5000, accessory); + drawNaelQuote_Circle(sid, 5000, 3000, accessory); + break; + } + case "6493": + { + // 月光啊!用你的炽热烧尽敌人! + drawNaelQuote_Donut(sid, 0, 5000, accessory); + drawNaelQuote_Stack(sid, 5000, 3000, accessory); + break; + } + case "6494": + { + // 被炽热灼烧过的轨迹,乃成铁血霸道! + drawNaelQuote_Stack(sid, 0, 5000, accessory); + drawNaelQuote_Circle(sid, 5000, 3000, accessory); + break; + } + case "6495": + { + // 炽热燃烧!给予我月亮的祝福! + drawNaelQuote_Stack(sid, 0, 5000, accessory); + drawNaelQuote_Donut(sid, 5000, 3000, accessory); + + break; + } + case "6496": + { + // 我降临于此,征战铁血霸道! + drawNaelQuote_Spread(sid, 0, 5000, accessory); + drawNaelQuote_Circle(sid, 5000, 3000, accessory); + break; + } + case "6497": + { + // 我降临于此,对月长啸! + drawNaelQuote_Spread(sid, 0, 5000, accessory); + drawNaelQuote_Donut(sid, 5000, 3000, accessory); + break; + } + case "6500": + { + // 超新星啊,更加闪耀吧!在星降之夜,称赞红月! + drawMeteorStream(sid, 12000, 3000, accessory); + drawNaelQuote_Tank(sid, 15000, 2000, accessory); + break; + } + case "6501": + { + // 超新星啊,更加闪耀吧!照亮红月下炽热之地! + drawNaelQuote_Tank(sid, 13000, 2000, accessory); + drawNaelQuote_Stack(sid, 15000, 2000, accessory); + break; + } + } + } + #endregion + + #region P2:小龙 + + [ScriptMethod(name: "P2奈尔:小龙位置记录(不可控)", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(816[34567])$"], userControl: false)] + public void CauterizePosRecord(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + lock (CauterizeDragons) + { + var pos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + var dir = PositionTo8Dir(pos, new(0, 0, 0)); + CauterizeDragons.Add(sid, dir); + } + } + + [ScriptMethod(name: "P2奈尔:小龙俯冲引导范围预警", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0014"])] + public void CauterizeTarget(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Nael) return; + + CauterizeTimes++; + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + + // 按照方向(dir)升序排序 CauterizeDragons + var sortedDragons = CauterizeDragons + .OrderBy(d => d.Value) // 根据dir升序排序 + .ToList(); + if (!showOtherCauterizeRoute && tid != accessory.Data.Me) return; + + switch (CauterizeTimes) + { + case 1: + CauterizeRouteDraw(sortedDragons[0].Key, tid, accessory); + CauterizeRouteDraw(sortedDragons[1].Key, tid, accessory); + break; + case 2: + CauterizeRouteDraw(sortedDragons[2].Key, tid, accessory); + break; + case 3: + CauterizeRouteDraw(sortedDragons[3].Key, tid, accessory); + CauterizeRouteDraw(sortedDragons[4].Key, tid, accessory); + break; + } + if (@event.TargetId != accessory.Data.Me) return; + CauterizeSafePosDraw(accessory); + } + private void CauterizeRouteDraw(uint sid, uint tid, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"俯冲引导{sid}"; + dp.Scale = new(20, 45); + dp.Owner = sid; + dp.DestoryAt = 6000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.5f); + dp.TargetObject = tid; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + var dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = $"俯冲引导方向{sid}"; + dp0.Scale = new(2.5f, 12.5f); + dp0.Owner = sid; + dp0.DestoryAt = 6000; + dp0.Color = accessory.Data.DefaultDangerColor.WithW(1f); + dp0.TargetObject = tid; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp0); + } + + private void CauterizeSafePosDraw(ScriptAccessory accessory) + { + var sortedDragonsDir = CauterizeDragons.OrderBy(d => d.Value).Select(d => d.Value + 1).ToArray(); + var sortedDragonsDirTuple = (sortedDragonsDir[0], sortedDragonsDir[1], sortedDragonsDir[2], sortedDragonsDir[3], sortedDragonsDir[4]); + var safePosClock = CauterizeSafePos.TryGetValue(sortedDragonsDirTuple, out var safePos) ? safePos : (0,0,0); + if (safePosClock.Equals((0,0,0))) + { + return; + } + var basePos = new Vector3(0, 0, -21); + int clock = CauterizeTimes switch + { + 1 => safePosClock.Item1, + 2 => safePosClock.Item2, + 3 => safePosClock.Item3, + _ => throw new IndexOutOfRangeException() + }; + var SafePos = RotatePoint(basePos, new(0, 0, 0), angle2Rad(clock * 30 % 360)); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"俯冲安全点"; + dp.Scale = new(2); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = SafePos; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "P2奈尔:小龙俯冲实际范围预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(993[12345])$"])] + public void CauterizeField(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "小龙俯冲"; + dp.Scale = new(20, 45); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = sid; + dp.DestoryAt = 3700; + dp.Color = colorCyan.V4.WithW(0.6f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + #endregion + + #region P3:巴哈(进军~灾厄) + + [ScriptMethod(name: "P3巴哈:【进军】阶段记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9954"], userControl: false)] + public void P3_PhaseChange_1st(Event @event, ScriptAccessory accessory) + { + // if (phase != UCOB_Phase.Nael) return; + phase = UCOB_Phase.Quickmarch_1st; + } + + // 双塔冲 + [ScriptMethod(name: "P3巴哈:双塔旋风冲", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9906"])] + public void TwistingDive(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "旋风冲范围"; + dp.Scale = new(8, 45); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor.WithW(3); + dp.Owner = sid; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + drawTwister(0, 5200, accessory); + } + + [ScriptMethod(name: "P3巴哈:奈尔月流冲", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9923"])] + public void LunarDive(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "月流冲"; + dp.Scale = new(8, 45); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor.WithW(3); + dp.Owner = sid; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "P3巴哈:巴哈百万核爆冲与分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9953"])] + public void MegaFlareDive(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "百万核爆冲"; + dp.Scale = new(12, 45); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor.WithW(3); + dp.Owner = sid; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + if (phase != UCOB_Phase.Quickmarch_1st) return; + for (var i = 0; i < 8; i++) + { + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "百万核爆冲"; + dp.Scale = new(5); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = accessory.Data.PartyList[i]; + dp.Delay = 4000; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "P3巴哈:大地摇动范围预警", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"])] + public void EarthShaker(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"大地摇动{tid}"; + dp.Position = new(0, 0, 0); + dp.Scale = new(50); + dp.Radian = float.Pi / 2; + dp.Color = accessory.Data.DefaultDangerColor; + dp.TargetObject = tid; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "P3巴哈:【进军】百万核爆冲位置记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9953"], userControl: false)] + public void MegaFlareDivePosRecord(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Quickmarch_1st) return; + QuickMarchPos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + } + + [ScriptMethod(name: "P3巴哈:【进军】临时北(俯冲起始点)指向提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9953"])] + public void MegaFlareDiveNorth(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Quickmarch_1st) return; + Task.Delay(100).ContinueWith(t => + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "进军12点标记"; + dp.Scale = new(1.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = posColorNormal.V4.WithW(2); + dp.Position = new(0, 0, 0); + dp.TargetPosition = QuickMarchPos; + dp.Delay = 4000; + dp.DestoryAt = 11000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + }); + } + + [ScriptMethod(name: "P3巴哈:【进军】核爆分摊指路", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0027"])] + public void QuickmarchStack(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + if (phase != UCOB_Phase.Quickmarch_1st) return; + if (QuickMarchStackDrawn) return; + + QuickMarchStackDrawn = true; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"核爆分摊提示{tid}"; + dp.Owner = tid; + dp.Scale = new(3); + dp.Radian = float.Pi / 2; + dp.Color = tid == accessory.Data.Me ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + // 核爆分摊位置提示 + var rad = FindRadian(new(0, 0, 0), QuickMarchPos); + var stackPos = ExtendPoint(new(0, 0, 0), rad, -6); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"核爆分摊位置{tid}"; + dp.Scale = new(3f); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = tid == accessory.Data.Me ? posColorPlayer.V4.WithW(3) : posColorNormal.V4.WithW(1); + dp.Position = stackPos; + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (tid != accessory.Data.Me) return; + accessory.Method.TextInfo("即将参与分摊", 5000, true); + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Scale = new(0.5f); + dp.Name = $"分摊位置指路{tid}"; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = stackPos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "P3巴哈:【进军】大地摇动站位指示", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"])] + public void EarthShakerDir(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + if (phase != UCOB_Phase.Quickmarch_1st) return; + if (tid != accessory.Data.Me) return; + if (QuickMarchEarthShakerDrawn) return; + + QuickMarchEarthShakerDrawn = true; + accessory.Method.RemoveDraw($"进军12点标记"); + var esPos_right = RotatePoint(QuickMarchPos, new(0, 0, 0), float.Pi / 2); + var esPos_left = RotatePoint(QuickMarchPos, new(0, 0, 0), -float.Pi / 2); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "大地摇动线指路-中"; + dp.Scale = new(1.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = getPlayerIdIndex(accessory, accessory.Data.Me) > 3 ? posColorPlayer.V4.WithW(3) : posColorNormal.V4.WithW(1); + dp.Position = new(0, 0, 0); + dp.TargetPosition = QuickMarchPos; + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "大地摇动线指路-右"; + dp.Scale = new(1.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = getPlayerIdIndex(accessory, accessory.Data.Me) == 3 ? posColorPlayer.V4.WithW(3) : posColorNormal.V4.WithW(1); + dp.Position = new(0, 0, 0); + dp.TargetPosition = esPos_right; + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "大地摇动线指路-左"; + dp.Scale = new(1.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = getPlayerIdIndex(accessory, accessory.Data.Me) == 2 ? posColorPlayer.V4.WithW(3) : posColorNormal.V4.WithW(1); + dp.Position = new(0, 0, 0); + dp.TargetPosition = esPos_left; + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + } + + [ScriptMethod(name: "P3巴哈:【黑炎】阶段记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9955"], userControl: false)] + public void P3_PhaseChange_2nd(Event @event, ScriptAccessory accessory) + { + // if (phase != UCOB_Phase.Quickmarch_1st) return; + phase = UCOB_Phase.Blackfire_2nd; + } + + [ScriptMethod(name: "P3巴哈:奈尔位置定位(不可控)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:8161"], userControl: false)] + public void NaelPosRecord(Event @event, ScriptAccessory accessory) + { + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + NaelPosition = spos; + } + + [ScriptMethod(name: "P3巴哈:【黑炎】奈尔位置指路", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:8161"])] + public void BlackFireNaelDir(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Blackfire_2nd) return; + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + Task.Delay(100).ContinueWith(t => + { + if (new Vector2(NaelPosition.X, NaelPosition.Z).Length() < 23) return; + if (blackFireDrawn) return; + if (DebugMode) + { + accessory.Method.SendChat($"/e 找到奈尔位置,在其脚下绘图,{new Vector2(NaelPosition.X, NaelPosition.Z).Length()}"); + var dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = $"奈尔位置"; + dp0.Scale = new(3f); + dp0.Color = posColorPlayer.V4.WithW(3); + dp0.Position = NaelPosition; + dp0.Delay = 0; + dp0.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "奈尔位置指路"; + dp.Scale = new(0.5f, 24); + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = accessory.Data.Me; + dp.TargetObject = sid; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + blackFireDrawn = true; + }); + } + + [ScriptMethod(name: "P3巴哈:【灾厄】阶段记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9956"], userControl: false)] + public void P3_PhaseChange_3rd(Event @event, ScriptAccessory accessory) + { + // if (phase != UCOB_Phase.Blackfire_2nd) return; + phase = UCOB_Phase.Fellruin_3rd; + } + + // 我降临于此对月长啸!召唤星降之夜! + [ScriptMethod(name: "P3巴哈:【灾厄】分散月环", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:6502"])] + public void FR_Spread_and_In(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + drawNaelQuote_Spread(sid, 0, 5000, accessory); + drawNaelQuote_Donut(sid, 5000, 3000, accessory); + } + + // 我自月而来降临于此,召唤星降之夜! + [ScriptMethod(name: "P3巴哈:【灾厄】月环分散", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:6503"])] + public void FR_In_and_Spread(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + drawNaelQuote_Donut(sid, 0, 5000, accessory); + drawNaelQuote_Spread(sid, 5000, 3000, accessory); + } + + [ScriptMethod(name: "P3巴哈:【灾厄】以太失控后陨石流", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9905"])] + public void AethericProfusion(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + drawMeteorStream(sid, 0, 4000, accessory); + } + + #endregion + + #region P3:巴哈(天地) + + [ScriptMethod(name: "P3巴哈:【天地】阶段记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9957"], userControl: false)] + public void P3_PhaseChange_4th(Event @event, ScriptAccessory accessory) + { + // if (phase != UCOB_Phase.Fellruin_3rd) return; + phase = UCOB_Phase.Heavensfall_4th; + } + + [ScriptMethod(name: "P3巴哈:【天地】起始位置记录(不可控)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:regex:^(8161|8159|8168)$"], userControl: false)] + public void HeavensFallPosRecord(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Heavensfall_4th) return; + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + float distance = new Vector2(spos.X, spos.Z).Length(); + if (distance < 23) return; + var idx = PositionTo8Dir(spos, new(0, 0, 0)); + + // 更新boss位置和危险位置 + HeavensFallBossPos[idx] = true; + + // 设置与idx对面的位置为危险 + HeavensFallDangerPos[idx] = true; + HeavensFallDangerPos[idx >= 4 ? idx - 4 : idx + 4] = true; + } + + [ScriptMethod(name: "P3巴哈:【天地】起始位置指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9906"])] + public void HeavensFallSafeDir(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Heavensfall_4th) return; + Task.Delay(100).ContinueWith(t => + { + // 此时,HeavensFallDangerPos中仅剩两个false,即为安全点。 + // 检查HeavensFallBossPos中,这两个false idx的前一个变量,如果是true,代表右边;如果是false,代表左边 + // 查找HeavensFallDangerPos中剩下的两个false + List safeIndices = new List(); + for (int i = 0; i < HeavensFallDangerPos.Count(); i++) + { + if (!HeavensFallDangerPos[i]) // 只关心值为false的索引 + safeIndices.Add(i); + } + + if (safeIndices.Count() == 2) + { + var MyIndex = getMyIndex(accessory); + // 检查HeavensFallBossPos中,安全位置的前一个位置 + foreach (var idx in safeIndices) + { + int prevIdx = idx == 0 ? 7 : idx - 1; // 如果idx是0,则前一个是7,否则是idx - 1 + // 判断前一个位置的boss是否存在 + bool isPrevTrue = HeavensFallBossPos[prevIdx]; + if (isPrevTrue) + { + // 如果前一个位置为true,代表该idx为三兄弟右边安全点,D2与D4 + Vector3 safePosition = RotatePoint(new Vector3(0, 0, -23), new Vector3(0, 0, 0), idx * float.Pi / 4); + HeavensFallSafeDirDraw(safePosition, MyIndex == 5 || MyIndex == 7, accessory); + if (DebugMode) + accessory.Method.SendChat($"/e {idx} 是右边的安全点"); + } + else + { + // 如果前一个位置为false,代表该idx为三兄弟左边安全点,MT与H1 + Vector3 safePosition = RotatePoint(new Vector3(0, 0, -23), new Vector3(0, 0, 0), idx * float.Pi / 4); + HeavensFallSafeDirDraw(safePosition, MyIndex == 0 || MyIndex == 2, accessory); + if (DebugMode) + accessory.Method.SendChat($"/e {idx} 是左边的安全点"); + } + } + + // 奈尔面前安全点,ST与H2 + var idx_nael = PositionTo8Dir(NaelPosition, new(0, 0, 0)); + Vector3 safePositionNael = RotatePoint(new Vector3(0, 0, -23), new Vector3(0, 0, 0), idx_nael * float.Pi / 4); + HeavensFallSafeDirDraw(safePositionNael, MyIndex == 1 || MyIndex == 3, accessory); + if (DebugMode) + accessory.Method.SendChat($"/e {idx_nael} 是奈尔面前的安全点"); + + // 奈尔身后安全点,D1与D3 + idx_nael = PositionTo8Dir(NaelPosition, new(0, 0, 0)); + idx_nael = idx_nael >= 4 ? idx_nael - 4 : idx_nael + 4; + safePositionNael = RotatePoint(new Vector3(0, 0, -23), new Vector3(0, 0, 0), idx_nael * float.Pi / 4); + HeavensFallSafeDirDraw(safePositionNael, MyIndex == 4 || MyIndex == 6, accessory); + if (DebugMode) + accessory.Method.SendChat($"/e {idx_nael} 是奈尔身后的安全点"); + } + }); + } + + private void HeavensFallSafeDirDraw(Vector3 safepos, bool isPlayerPos, ScriptAccessory accessory) + { + if (isPlayerPos) + { + var dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = "天地安全位置指路"; + dp0.Scale = new(0.5f, 22); + dp0.ScaleMode |= ScaleMode.YByDistance; + dp0.Color = accessory.Data.DefaultSafeColor; + dp0.Owner = accessory.Data.Me; + dp0.TargetPosition = safepos; + dp0.Delay = 0; + dp0.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "天地安全位置"; + dp.Scale = new(4); + dp.Color = isPlayerPos ? posColorPlayer.V4.WithW(3) : posColorNormal.V4; + dp.Position = safepos; + dp.Delay = 0; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "P3巴哈:【天地】塔位置记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9951"], userControl: false)] + public void HeavensFallTower(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Heavensfall_4th) return; + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + var idx_tower = PositionTo16Dir(spos, new(0, 0, 0)); + HeavensFallTowerPos[idx_tower] = true; + } + + [ScriptMethod(name: "P3巴哈:【天地】塔位置指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9951"])] + public void HeavensFallTowerDir(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Heavensfall_4th) return; + + Task.Delay(100).ContinueWith(t => + { + if (HeavensFallTowerDrawn) return; + HeavensFallTowerDrawn = true; + var naelIdx = PositionTo16Dir(NaelPosition, new(0, 0, 0)); + var towerJudgeIdx = naelIdx; + var towerPlayerIdxCount = 0; + var MyIndex = getMyIndex(accessory); + List towerPlayerTarget = [7, 0, 6, 1, 5, 2, 4, 3]; + + for (int i = 0; i < HeavensFallTowerPos.Count(); i++) + { + if (DebugMode) + { + accessory.Method.SendChat($"/e 正在寻找位置{towerJudgeIdx}的塔,是第{towerPlayerIdxCount}座塔。"); + } + if (HeavensFallTowerPos[towerJudgeIdx]) // 找到塔的位置 + { + if (towerPlayerIdxCount == towerPlayerTarget[MyIndex]) + { + // 找到了,就是他! + var dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = $"目标塔{towerJudgeIdx}"; + dp0.Scale = new(3f); + dp0.Color = posColorPlayer.V4.WithW(3); + dp0.Position = RotatePoint(new Vector3(0, 0, -22), new Vector3(0, 0, 0), towerJudgeIdx * float.Pi / 8); + dp0.Delay = 0; + dp0.DestoryAt = 6500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + + dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = $"击退位置{towerJudgeIdx}"; + dp0.Scale = new(1f); + dp0.Color = posColorPlayer.V4; + dp0.Position = RotatePoint(new Vector3(0, 0, -10), new Vector3(0, 0, 0), towerJudgeIdx * float.Pi / 8); + dp0.Delay = 0; + dp0.DestoryAt = 6500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + + dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = "天地击退塔位置指路"; + dp0.Scale = new(0.5f); + dp0.ScaleMode |= ScaleMode.YByDistance; + dp0.Color = accessory.Data.DefaultSafeColor; + dp0.Owner = accessory.Data.Me; + dp0.TargetPosition = RotatePoint(new Vector3(0, 0, -10), new Vector3(0, 0, 0), towerJudgeIdx * float.Pi / 8); + dp0.Delay = 0; + dp0.DestoryAt = 6500; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + towerPlayerIdxCount++; + } + towerJudgeIdx++; + if (towerJudgeIdx == HeavensFallTowerPos.Count()) + towerJudgeIdx = 0; + } + }); + } + + [ScriptMethod(name: "P3巴哈:【天地】中心塔与击退指示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9911"])] + public void HeavensFallMiddleTower(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "天崩地裂"; + dp.Scale = new(4); + dp.ScaleMode = ScaleMode.ByTime; + dp.Position = new(0, 0, 0); + dp.DestoryAt = 5000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp2 = accessory.Data.GetDefaultDrawProperties(); + dp2.Name = "天崩地裂击退"; + dp2.Scale = new(1.5f, 12); + dp2.Owner = accessory.Data.Me; + dp2.TargetPosition = new(0, 0, 0); + dp2.Rotation = float.Pi; + dp2.Color = accessory.Data.DefaultSafeColor; + dp2.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp2); + } + + #endregion + #region P3:巴哈(连击) + + [ScriptMethod(name: "P3巴哈:【连击】阶段记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9958"], userControl: false)] + public void P3_PhaseChange_5th(Event @event, ScriptAccessory accessory) + { + // if (phase != UCOB_Phase.Heavensfall_4th) return; + phase = UCOB_Phase.Tenstrike_5th; + } + + [ScriptMethod(name: "P3巴哈:【连击】逐个陨石流预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9958"])] + public void TenStrikeMeteorStream(Event @event, ScriptAccessory accessory) + { + Task.Delay(7500).ContinueWith(t => + { + if (phase != UCOB_Phase.Tenstrike_5th) return; + for (var i = 0; i < 8; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"陨石流{i}"; + dp.Scale = new(4); + dp.Owner = accessory.Data.PartyList[i]; + dp.DestoryAt = 15000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + }); + } + + [ScriptMethod(name: "P3巴哈:【连击】场中-拘束器-场边三点一线指引", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9958"])] + public void TenStrikeOrbTargetRoute(Event @event, ScriptAccessory accessory) + { + Task.Delay(8500).ContinueWith(t => + { + if (phase != UCOB_Phase.Tenstrike_5th) return; + var MyIndex = getMyIndex(accessory); + for (var i = 0; i < 3; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"三点一线{i}"; + dp.Scale = new(2f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Position = new(0, 0, 0); + dp.TargetPosition = new(RestrictorPos[i].X / RestrictorPos[i].Length() * 24, 0, RestrictorPos[i].Z / RestrictorPos[i].Length() * 24); + dp.DestoryAt = 10000; + dp.Color = isTenStrikeTarget ? posColorPlayer.V4.WithW(2f) : colorRed.V4.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + }); + } + + [ScriptMethod(name: "P3巴哈:【连击】逐个陨石流预警删除(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9920", "TargetIndex:1"], userControl: false)] + public void MB_TyrantsFire(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Tenstrike_5th) return; + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + var tidx = getPlayerIdIndex(accessory, tid); + accessory.Method.RemoveDraw($"陨石流{tidx}"); + } + + [ScriptMethod(name: "P3巴哈:【连击】撞球截球提示与指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"])] + public void TenStrikeBlackOrbDir(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + if (phase != UCOB_Phase.Tenstrike_5th) return; + if (TenStrikeBlackOrbDrawn) return; + + int trueCount = 0; + for (int i = 0; i < GenerateTarget.Count(); i++) + { + if (GenerateTarget[i]) + trueCount++; + } + if (trueCount != 3) return; + TenStrikeBlackOrbDrawn = true; + + // 算出每组黑球玩家数目,给出提示信息 + List TenStrikeBlackOrbGroupTargetNum = calcTenStrikeGroupTargetNum(GenerateTarget); + if (DebugMode) + { + string tenStrikeTargetNum = string.Join(", ", TenStrikeBlackOrbGroupTargetNum); + accessory.Method.SendChat($"/e 每组黑球玩家数:{tenStrikeTargetNum}"); + } + + if (showGlobalTenStrikeBlackOrbMsg) + showTenStrikeBlackOrbMsg(TenStrikeBlackOrbGroupTargetNum, accessory); + + // 计算撞球与截球优先级,输出任务List + // 注意,截球优先级并无唯一规则,需要观察场上情况灵活变通。 + List missionList = judgeTenStrikeBlackOrbRoute(GenerateTarget); + if (showGlobalTenStrikeBlackOrbMsg) + { + string mission_record = ""; + mission_record += $"H1组:撞球{getPlayerJobIndexByIdx(missionList[0])}, 截球{getPlayerJobIndexByIdx(missionList[1])}\n"; + mission_record += $"H2组:撞球{getPlayerJobIndexByIdx(missionList[2])}, 截球{getPlayerJobIndexByIdx(missionList[3])}\n"; + mission_record += $"D3D4组:撞球{getPlayerJobIndexByIdx(missionList[4])}, 截球{getPlayerJobIndexByIdx(missionList[5])}\n"; + accessory.Method.SendChat($"/e {mission_record}"); + } + + int routeDestoryTime1 = 5000; // 第一次指路持续时间 & 第二次指路出现时间 + int routeDestoryTime2 = 5000; // 第二次指路(截球人)持续时间 + // 拘束器idx:0为H1组D,1为D3D4组C,2为H2组B + drawBlackOrbRoute(missionList[0], 0, 0, routeDestoryTime1, true, accessory); + drawBlackOrbRoute(missionList[1], 0, 0, routeDestoryTime1, false, accessory); + drawBlackOrbRoute(missionList[1], 0, routeDestoryTime1, routeDestoryTime2, true, accessory); + drawBlackOrbRoute(missionList[2], 2, 0, routeDestoryTime1, true, accessory); + drawBlackOrbRoute(missionList[3], 2, 0, routeDestoryTime1, false, accessory); + drawBlackOrbRoute(missionList[3], 2, routeDestoryTime1, routeDestoryTime2, true, accessory); + drawBlackOrbRoute(missionList[4], 1, 0, routeDestoryTime1, true, accessory); + drawBlackOrbRoute(missionList[5], 1, 0, routeDestoryTime1, false, accessory); + drawBlackOrbRoute(missionList[5], 1, routeDestoryTime1, routeDestoryTime2, true, accessory); + } + + private List calcTenStrikeGroupTargetNum(List targets) + { + List TenStrikeBlackOrbGroupTargetNum = [0, 0, 0]; + for (int i = 0; i < 8; i++) + { + if (targets[i]) + { + if (i == 0 | i == 2 | i == 4) + TenStrikeBlackOrbGroupTargetNum[0]++; + else if (i == 1 | i == 3 | i == 5) + TenStrikeBlackOrbGroupTargetNum[1]++; + else if (i == 6 | i == 7) + TenStrikeBlackOrbGroupTargetNum[2]++; + } + } + return TenStrikeBlackOrbGroupTargetNum; + } + + private void showTenStrikeBlackOrbMsg(List targetNum, ScriptAccessory accessory) + { + // TODO 确认消息持续时间 + int msgDuration = 8000; + switch (targetNum) + { + case [1, 1, 1]: + accessory.Method.TextInfo($"每组1人黑球,无需换位", msgDuration, false); + break; + case [2, 1, 0]: + accessory.Method.TextInfo($"【H1组】:2人黑球,换位至【D3D4组】", msgDuration, true); + break; + case [2, 0, 1]: + accessory.Method.TextInfo($"【H1组】:2人黑球,换位至【H2组】", msgDuration, true); + break; + case [1, 2, 0]: + accessory.Method.TextInfo($"【H2组】:2人黑球,换位至【D3D4组】", msgDuration, true); + break; + case [0, 2, 1]: + accessory.Method.TextInfo($"【H2组】:2人黑球,换位至【H1组】", msgDuration, true); + break; + case [1, 0, 2]: + accessory.Method.TextInfo($"【D3D4组】:2人黑球,换位至【H2组】\n【H1/H2组】:补截球【D3D4组】", msgDuration, true); + break; + case [0, 1, 2]: + accessory.Method.TextInfo($"【D3D4组】:2人黑球,换位至【H1组】\n【H1/H2组】:补截球【D3D4组】", msgDuration, true); + break; + case [3, 0, 0]: + accessory.Method.TextInfo($"【H1组】:3人黑球,换位至其他两组\n【H2/D3D4组】:补截球【H1组】", msgDuration, true); + break; + case [0, 3, 0]: + accessory.Method.TextInfo($"【H2组】:3人黑球,换位至其他两组\n【H1/D3D4组】:补截球【H2组】", msgDuration, true); + break; + } + } + + private List judgeTenStrikeBlackOrbRoute(List targets) + { + // 计算任务分配 + // [H1组拘束器撞球,截球,H2组拘束器撞球,截球,D3D4组拘束器撞球,截球] + List missionList = [-1, -1, -1, -1, -1, -1]; + + // 三组撞球与截球的优先级 + // H1 MT D1 D3 ST D4 D2 H2 + List priority_group1 = [2, 0, 4, 6, 1, 7, 5, 3]; + // H2 ST D2 D4 MT D3 D1 H1 + List priority_group2 = [3, 1, 5, 7, 0, 6, 4, 2]; + // D3 D4 D1 D2 MT ST H1 H2 + List priority_group3 = [6, 7, 4, 5, 0, 1, 2, 3]; + + // 第一轮判断 撞球人 + for (int i = 0; i < 8; i++) + { + // 如果 判断目标被黑球点名 & 判断目标不在被分配任务的list中 & 对应黑球位置目标仍未确定 + if (targets[priority_group1[i]] && !missionList.Contains(priority_group1[i]) && missionList[0] == -1) + missionList[0] = priority_group1[i]; + if (targets[priority_group2[i]] && !missionList.Contains(priority_group2[i]) && missionList[2] == -1) + missionList[2] = priority_group2[i]; + if (targets[priority_group3[i]] && !missionList.Contains(priority_group3[i]) && missionList[4] == -1) + missionList[4] = priority_group3[i]; + } + + // 第二轮判断 截球人 + for (int i = 0; i < 8; i++) + { + // 如果 判断目标未被黑球点名 & 判断目标不在被分配任务的list中 & 对应截球位置目标仍未确定 + if (!targets[priority_group1[i]] && !missionList.Contains(priority_group1[i]) && missionList[1] == -1) + missionList[1] = priority_group1[i]; + if (!targets[priority_group2[i]] && !missionList.Contains(priority_group2[i]) && missionList[3] == -1) + missionList[3] = priority_group2[i]; + if (!targets[priority_group3[i]] && !missionList.Contains(priority_group3[i]) && missionList[5] == -1) + missionList[5] = priority_group3[i]; + } + + return missionList; + } + + public static string getPlayerJobIndexByIdx(int idx) + { + switch (idx) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } + + [ScriptMethod(name: "P3巴哈:【连击】大地摇动指路", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"])] + public void TenstrikeEarthShakerDir(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.Tenstrike_5th) return; + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + TenStrikeEarthShakerNum++; + + lock (this) + { + Task.Delay(100).ContinueWith(t => + { + if (DebugMode) + accessory.Method.SendChat($"/e 检测到TenStrikeEarthShakerNum:{TenStrikeEarthShakerNum}"); + + if (TenStrikeEarthShakerNum != 4) return; + + if (tid == accessory.Data.Me) + isEarthShakerFirstRound = true; + + Vector3 safePosition = new(0, 0, -24); + Vector3 safePosition1 = RotatePoint(safePosition, new(0, 0, 0), float.Pi / 180 * 80); + Vector3 safePosition2 = RotatePoint(safePosition, new(0, 0, 0), -float.Pi / 180 * 80); + Vector3 safePosition3 = RotatePoint(safePosition, new(0, 0, 0), float.Pi / 180 * 140); + Vector3 safePosition4 = RotatePoint(safePosition, new(0, 0, 0), -float.Pi / 180 * 140); + + Vector3 safePosition5 = RotatePoint(safePosition, new(0, 0, 0), float.Pi / 180 * 40); + Vector3 safePosition6 = RotatePoint(safePosition, new(0, 0, 0), -float.Pi / 180 * 40); + Vector3 safePosition7 = RotatePoint(safePosition, new(0, 0, 0), float.Pi / 180 * 100); + Vector3 safePosition8 = RotatePoint(safePosition, new(0, 0, 0), -float.Pi / 180 * 100); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "连击安全区标记"; + dp.Scale = new(1.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = posColorNormal.V4.WithW(3); + dp.Position = new(0, 0, 0); + dp.DestoryAt = 5000; + + if (isEarthShakerFirstRound) + { + dp.Delay = 0; + dp.TargetPosition = safePosition1; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + dp.TargetPosition = safePosition2; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + dp.TargetPosition = safePosition3; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + dp.TargetPosition = safePosition4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + else + { + dp.Delay = 5000; + dp.TargetPosition = safePosition5; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + dp.TargetPosition = safePosition6; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + dp.TargetPosition = safePosition7; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + dp.TargetPosition = safePosition8; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + }); + } + } + + #endregion + #region P3:巴哈(群龙) + + [ScriptMethod(name: "P3巴哈:【群龙】阶段记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9959"], userControl: false)] + public void P3_PhaseChange_6th(Event @event, ScriptAccessory accessory) + { + // if (phase != UCOB_Phase.Heavensfall_5th) return; + phase = UCOB_Phase.GrandOctet_6th; + } + + [ScriptMethod(name: "P3巴哈:双塔位置定位(不可控)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:8159"], userControl: false)] + public void TwintaniaPosRecord(Event @event, ScriptAccessory accessory) + { + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + TwintaniaPosition = spos; + } + + [ScriptMethod(name: "P3巴哈:巴哈位置定位(不可控)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:8168"], userControl: false)] + public void BahamutPosRecord(Event @event, ScriptAccessory accessory) + { + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + BahamutPosition = spos; + } + + [ScriptMethod(name: "P3巴哈:【群龙】起始位置绘图", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:8168"])] + public void GrandOctStartPosDir(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.GrandOctet_6th) return; + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + Task.Delay(100).ContinueWith(t => + { + float CalculateDistance(Vector3 position) => new Vector2(position.X, position.Z).Length(); + if (CalculateDistance(BahamutPosition) < 23 || CalculateDistance(TwintaniaPosition) < 23 || CalculateDistance(NaelPosition) < 23) return; + + if (grandOctDrawn) return; + + if (DebugMode) + { + accessory.Method.SendChat($"/e 找到巴哈位置,在其脚下绘图,{CalculateDistance(BahamutPosition)}"); + var dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = $"巴哈位置"; + dp0.Scale = new(3f); + dp0.Color = posColorNormal.V4; + dp0.Position = BahamutPosition; + dp0.Delay = 0; + dp0.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + } + + var BahamutDir = PositionTo8Dir(BahamutPosition, new(0, 0, 0)); + var NaelDir = PositionTo8Dir(NaelPosition, new(0, 0, 0)); + + bool isAdvanced = Math.Abs(BahamutDir - NaelDir) == 4; // 巴哈对面有奈尔,提前一格 + bool isTurnLeft = BahamutDir % 2 == 0; // 巴哈在偶数面向(正点),面向场外往左跑 + var startDir = BahamutDir > 3 ? BahamutDir - 4 : BahamutDir + 4; + if (isAdvanced) + { + startDir = isTurnLeft ? startDir - 1 : startDir + 1; + } + + startDir = (startDir == -1) ? 7 : (startDir == 8) ? 0 : startDir; + + if (DebugMode) + { + accessory.Method.SendChat($"/e 起跑位置为:{startDir}"); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"群龙起跑位置"; + dp.Scale = new(5f); + dp.Color = posColorPlayer.V4.WithW(3); + dp.Position = RotatePoint(new Vector3(0, 0, -23), new Vector3(0, 0, 0), startDir * float.Pi / 4); + dp.Delay = 0; + dp.DestoryAt = 6500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"群龙起跑位置指路-危险"; + dp.Scale = new(0.5f); + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = RotatePoint(new Vector3(0, 0, -23), new Vector3(0, 0, 0), startDir * float.Pi / 4); + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + accessory.Method.TextInfo("等待标记出现后,前往起跑点", 3000); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"群龙起跑位置指路-安全"; + dp.Scale = new(0.5f); + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = RotatePoint(new Vector3(0, 0, -23), new Vector3(0, 0, 0), startDir * float.Pi / 4); + dp.Delay = 5000; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + grandOctDrawn = true; + }); + } + + [ScriptMethod(name: "P3巴哈:【群龙】点名记录(不可控)", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0077|0029|0014)$"], userControl: false)] + public void GrandOctTargetRecord(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.GrandOctet_6th) return; + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + grandOctIconNum++; + grandOctTargetChosen[getPlayerIdIndex(accessory, tid)] = true; + } + + [ScriptMethod(name: "P3巴哈:【群龙】起跑提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9923"])] + public void GrandOctStartMention(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.GrandOctet_6th) return; + + var BahamutDir = PositionTo8Dir(BahamutPosition, new(0, 0, 0)); + bool isTurnLeft = BahamutDir % 2 == 0; + if (isTurnLeft) + accessory.Method.TextInfo("等待奈尔冲锋后,面向场外向【左】跑", 3000); + else + accessory.Method.TextInfo("等待奈尔冲锋后,面向场外向【右】跑", 3000); + } + + [ScriptMethod(name: "P3巴哈:【群龙】回中提示、双塔位置及引导提示", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0029"])] + public void GrandOctTarget(Event @event, ScriptAccessory accessory) + { + if (phase != UCOB_Phase.GrandOctet_6th) return; + Task.Delay(100).ContinueWith(t => + { + if (grandOctIconNum != 7) return; + var MyIndex = getMyIndex(accessory); + var TwintaniaTargetIdx = 0; + for (int i = 0; i < grandOctTargetChosen.Count(); i++) + { + if (!grandOctTargetChosen[i]) // 只关心值为false的索引 + TwintaniaTargetIdx = i; + } + if (MyIndex == TwintaniaTargetIdx) + { + accessory.Method.TextInfo("回中,准备引导双塔尼亚", 3000, true); + } + else + { + accessory.Method.TextInfo("回中,寻找双塔尼亚,观察点名", 3000, true); + } + + if (showOtherCauterizeRoute || MyIndex == TwintaniaTargetIdx) + { + var dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = $"群龙双塔俯冲引导"; + dp0.Scale = new(8, 45); + dp0.Position = TwintaniaPosition; + dp0.TargetObject = accessory.Data.PartyList[TwintaniaTargetIdx]; + dp0.Delay = 3500; + dp0.DestoryAt = 5500; + dp0.Color = accessory.Data.DefaultDangerColor.WithW(0.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp0); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"群龙双塔位置标记"; + dp.Scale = new(1.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = posColorNormal.V4.WithW(2); + dp.Position = new(0, 0, 0); + dp.TargetPosition = TwintaniaPosition; + dp.Delay = 2000; + dp.DestoryAt = 3500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + }); + } + + #endregion + + #region P4:亿万核爆 + + [ScriptMethod(name: "P4双打:阶段记录(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9960", "TargetIndex:2"], userControl: false)] + public void P4_PhaseChange(Event @event, ScriptAccessory accessory) + { + phase = UCOB_Phase.BahamutFavor; + if (DebugMode) + { + accessory.Method.SendChat($"/e 检测到进入P4。"); + } + } + private void drawBlackOrbRoute(int pidx, int residx, int delay, int destoryAt, bool isSafe, ScriptAccessory accessory) + { + // pidx: 玩家index + // residx:拘束器index + if (pidx != getMyIndex(accessory)) return; + + if (phase == UCOB_Phase.BahamutFavor) + { + if (residx == 0) + accessory.Method.TextInfo("先躲避旋风,后撞球", 3000, false); + else + accessory.Method.TextInfo("先撞球,后躲避旋风", 3000, false); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"拘束器位置定位{residx}"; + dp.Scale = new(1.5f); + dp.Position = RestrictorPos[residx]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = posColorPlayer.V4.WithW(3); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Scale = new(0.5f); + dp.Name = $"拘束器撞球玩家{pidx}指路"; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = RestrictorPos[residx]; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = isSafe ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "P4双塔:踩拘束器指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"])] + public void BlackOrbDir(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + if (phase != UCOB_Phase.BahamutFavor) return; + int trueCount = 0; + for (int i = 0; i < GenerateTarget.Count(); i++) + { + if (GenerateTarget[i]) + trueCount++; + } + if (trueCount != 3) return; + + // 判断4/5/6是否为true,如果是,DoSomething(4/5/6) + // P4的黑球,D1(B),D2(C),D3(D),D4(补) + if (GenerateTarget[4]) + drawBlackOrbRoute(4, 2, 0, 5500, true, accessory); + else + drawBlackOrbRoute(7, 2, 0, 5500, true, accessory); + + if (GenerateTarget[5]) + drawBlackOrbRoute(5, 1, 0, 5500, true, accessory); + else + drawBlackOrbRoute(7, 1, 0, 5500, true, accessory); + + if (GenerateTarget[6]) + drawBlackOrbRoute(6, 0, 0, 5500, true, accessory); + else + drawBlackOrbRoute(7, 0, 0, 5500, true, accessory); + } + + private void drawBahamutFavorNorth(int delay, int destoryAt, ScriptAccessory accessory) + { + switch (BahamutFavorNorth) + { + case BahamutFavorNorthTypeEnum.画出12点_ShowTempNorth: + drawBahamutFavorTempNorth(delay, destoryAt, accessory); + break; + case BahamutFavorNorthTypeEnum.画出8条分散方向_ShowRecomDir: + drawBahamutFavorSpreadDir(delay, destoryAt, accessory); + break; + case BahamutFavorNorthTypeEnum.不画_DontDraw: + default: + return; + } + } + + private void drawBahamutFavorTempNorth(int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"P4的12点标记"; + dp.Scale = new(1.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = posColorNormal.V4.WithW(2); + dp.Position = new(0, 0, 0); + dp.TargetPosition = BahamutFavorPos; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + private void drawBahamutFavorSpreadDir(int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + var initRad = FindRadian(new(0, 0, 0), BahamutFavorPos); + List angles = [-20, 20, -105, 105, -60, 60, -150, 150]; + + for (int i = 0; i < 8; i++) + { + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"P4分散方向标记{i}"; + dp.Scale = new(1.25f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = i == getMyIndex(accessory) ? posColorPlayer.V4.WithW(5f) : posColorNormal.V4.WithW(5f); + dp.Position = new(0, 0, 0); + dp.TargetPosition = ExtendPoint(new Vector3(0, 0, 0), initRad + angle2Rad(angles[i]), 20); + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + } + + [ScriptMethod(name: "P4奈尔:台词与临时北方向指引", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(650[4567])$"])] + public void NaelQuotesP4(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var quoteId = @event["Id"]; + + switch (quoteId) + { + case "6504": + { + // 钢铁燃烧吧!成为我降临于此的刀剑吧! + // 钢铁、分摊、分散 + drawNaelQuote_Circle(sid, 0, 5000, accessory); + drawNaelQuote_Stack(sid, 5000, 3000, accessory); + // 分摊结束后即可指示方向 + drawBahamutFavorNorth(8000, 8000, accessory); + drawNaelQuote_Spread(sid, 8000, 3000, accessory); + break; + } + case "6505": + { + // 钢铁成为我降临于此的燃烧之剑! + // 钢铁、分散、分摊 + drawNaelQuote_Circle(sid, 0, 5000, accessory); + drawNaelQuote_Spread(sid, 5000, 3000, accessory); + drawNaelQuote_Stack(sid, 8000, 3000, accessory); + // 分摊结束后即可指示方向 + drawBahamutFavorNorth(11000, 5000, accessory); + break; + } + case "6506": + { + // 我自月而来降临于此,踏过炽热之地! + // 月环、分散、分摊 + drawNaelQuote_Donut(sid, 0, 5000, accessory); + drawNaelQuote_Spread(sid, 5000, 3000, accessory); + drawNaelQuote_Stack(sid, 8000, 3000, accessory); + // 分摊结束后即可指示方向 + drawBahamutFavorNorth(11000, 5000, accessory); + break; + } + case "6507": + { + // 我自月而来携钢铁降临于此! + // 月环、钢铁、分散 + drawNaelQuote_Donut(sid, 0, 5000, accessory); + drawNaelQuote_Circle(sid, 5000, 3000, accessory); + // 分摊结束后即可指示方向 + drawBahamutFavorNorth(8000, 8000, accessory); + drawNaelQuote_Spread(sid, 8000, 3000, accessory); + break; + } + } + } + + #endregion + + #region P5:重生绝境 + + [ScriptMethod(name: "P5黄金:阶段记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9964"], userControl: false)] + public void P5_PhaseChange(Event @event, ScriptAccessory accessory) + { + if (phase == UCOB_Phase.FlamesRebirth) return; + phase = UCOB_Phase.FlamesRebirth; + } + + [ScriptMethod(name: "P5黄金:无尽顿悟分摊目标", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9964"])] + public void MornAfahStack(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + MornAfahNum++; + + if (showStackBusterNum) + accessory.Method.TextInfo($"无尽顿悟(分摊)#{MornAfahNum}", 4000, true); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"无尽顿悟分摊"; + dp.Scale = new(4); + dp.Owner = tid; + dp.DestoryAt = 4000; + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "P5黄金:死亡轮回死刑目标", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9962"])] + public void AkhMornTankBuster(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + ArkMornNum++; + + if (showStackBusterNum) + accessory.Method.TextInfo($"死亡轮回(死刑)#{ArkMornNum}", 4000, true); + + var MyIndex = getMyIndex(accessory); + var isTank = MyIndex == 0 || MyIndex == 1; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"死亡轮回死刑"; + dp.Scale = new(4); + dp.Owner = tid; + dp.DestoryAt = 4000; + dp.Color = isTank ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "P5黄金:百京核爆预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9968"])] + public void Exaflare(Event @event, ScriptAccessory accessory) + { + var srot = JsonConvert.DeserializeObject(@event["SourceRotation"]); + var spos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + + for (int i = 0; i < 6; i++) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"百京核爆{i}"; + dp.Scale = new(6); + // dp.Rotation = srot; + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = exflareColor.V4.WithW(3); + dp.Position = ExtendPoint(spos, float.Pi - srot, 8 * i); + dp.Delay = i == 0 ? 0 : 4000 + 1500 * (i - 1); + dp.DestoryAt = i == 0 ? 4000 : 1500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + drawExflareWarn(1, i, spos, srot, accessory); + drawExflareWarn(2, i, spos, srot, accessory); + } + } + + private void drawExflareWarn(uint idx, int iter_i, Vector3 spos, float srot, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"百京核爆预警{idx}-{iter_i}"; + dp.Scale = new(6); + dp.Color = exflareWarnColor.V4.WithW(0.8f / idx); + dp.Position = ExtendPoint(spos, float.Pi - srot, 8 * (iter_i + idx)); + dp.Delay = iter_i == 0 ? 0 : 4000 + 1500 * (iter_i - 1); + dp.DestoryAt = 1500 * (idx - 1) + (iter_i == 0 ? 4000 : 1500); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion +} \ No newline at end of file diff --git a/lianying1997/04-StormBlood/UcobReborn/UcobReborn.cs b/lianying1997/04-StormBlood/UcobReborn/UcobReborn.cs new file mode 100644 index 0000000..1e98ef5 --- /dev/null +++ b/lianying1997/04-StormBlood/UcobReborn/UcobReborn.cs @@ -0,0 +1,4781 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.Globalization; +using System.Runtime.CompilerServices; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; + +namespace UsamisKodakku.Scripts._04_StormBlood.UcobReborn; + +[ScriptType(name: Name, territorys: [733], guid: "e2e37136-72a2-46b0-abc7-ade17da161b7", + version: Version, author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +public class UcobReborn +{ + const string NoteStr = + $""" + {Version} + 基于 UCOB [巴哈姆特绝境战] 脚本的重置版。 + 加了很多很多东西。 + 原脚本作者:Joshua,Meva,KnightRider,Usami + """; + + const string UpdateInfo = + $""" + {Version} + 1. 移除了 P3F_踩塔判定倒计时。 + """; + + private const string Name = "绝巴哈姆特 Reborn"; + private const string Version = "0.0.0.4"; + private const string DebugVersion = "g"; + private int _runId = 0; + public const bool Debugging = false; + + public static readonly Vector3 Center = Vector3.Zero; + + private UcobParams _upm = new(); + private PriorityDict _pd = new(); + private readonly object _stateLock = new(); + + [UserSetting("特殊模式,含调用游戏原生特效的绘图")] + public static bool SpecialMode { get; set; } = true; + + // [UserSetting("指挥模式")] + // public static bool CaptainMode { get; set; } = false; + + public void Init(ScriptAccessory sa) + { + _runId++; + DrawTools.ResetLifecycle(); + _upm.Reset(); + _pd.Init("P1黑球"); + sa.Method.RemoveDraw(".*"); + sa.Method.ClearFrameworkUpdateAction(this); + sa.DebugMsg($"脚本 {Name} v{Version}{DebugVersion} 完成初始化,_runId {_runId}"); + } + + + private async Task WaitUntilConditions( + Func[] conditions, + int timeoutMs = 2000, + int intervalMs = 50) + { + var runId = _runId; + var count = timeoutMs / intervalMs; + for (int i = 0; i < count; i++) + { + if (_runId != runId) return false; + lock (_stateLock) + { + if (conditions.All(condition => condition())) + return _runId == runId; + } + await Task.Delay(intervalMs); + } + return false; + } + + #region 测试项 + + [ScriptMethod(name: "———————— 《测试项》 ————————", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 测试项分割线(Event ev, ScriptAccessory sa) + { + sa.DebugMsg($"Hello Koda! {Name}"); + } + + [ScriptMethod(name: "拉怪位置", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 拉怪位置(Event ev, ScriptAccessory sa) + { + _upm.求解拉怪位置(); + + for (int i = 0; i < 2; i++) + { + if (!Debugging && sa.GetMyIndex() != i) continue; + sa.DrawGuidance(sa.Data.PartyList[i], _upm.拉怪位置, 0, 5000, + $"P4_{_upm.当前阶段}_拉怪位置", sa.Data.DefaultSafeColor); + } + + var color = new Vector4(1f, 0.5f, 0.5f, 0.75f); + sa.DrawCircle(_upm.拉怪位置, 0, 5000, $"P4_{_upm.当前阶段}_拉怪位置", 1f, color); + + 执行分散方向绘图(sa, 0, 5000); + } + + [ScriptMethod(name: "测试模板", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26071"], + userControl: Debugging)] + public void 测试模板(Event ev, ScriptAccessory sa) + { + sa.DebugMsg($"hello"); + + lock (_stateLock) + { + _upm.P3.塔头标偏移++; + sa.DrawCountDown(ev.TargetPosition, 3000, iconScale: 1f, objIdBias: _upm.P3.塔头标偏移); + } + } + + #endregion 测试项 + + #region 通用 双塔尼亚 + + [ScriptMethod(name: "=============《通用 双塔尼亚》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 双塔尼亚_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "GEN_旋风预警", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9898|9906)$"], + userControl: true)] + public void GEN_旋风预警(Event ev, ScriptAccessory sa) + { + var destroyMs = ev.ActionId == 9898 ? 2000 : 5500; + for (var i = 0; i < sa.Data.PartyList.Count; i++) + sa.DrawCircle(sa.Data.PartyList[i], 0, destroyMs, $"GEN_{_upm.当前阶段}_旋风{i}", + 1.5f, sa.Data.DefaultDangerColor.WithW(2), byTime: true); + + var txt = _upm.当前阶段 < 1000 ? "旋风 -> 分摊" : "旋风旋风"; + var ttstxt = _upm.当前阶段 < 1000 ? "旋风然后分摊" : "旋风旋风"; + + sa.TextInfo(txt, destroyMs: 2000, isWarning: true); + sa.TTS(ttstxt); + } + + [ScriptMethod(name: "GEN_旋风危险区", + eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2001168", "Operate:Add"], + userControl: true)] + public void GEN_旋风危险区(Event ev, ScriptAccessory sa) + { + sa.DrawCircle(ev.SourcePosition, 0, 7000, $"GEN_旋风危险区", 1.25f, new Vector4(1, 0, 0, 4)); + } + + [ScriptMethod(name: "GEN_黑球路径", + eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:8160"], + userControl: true)] + public void GEN_黑球路径(Event ev, ScriptAccessory sa) + { + var sid = ev.SourceId; + var dp = sa.DrawLine(sid, 0, 3500, 10000, $"GEN_黑球路径{sid}", + 0, 2f, 6f, new Vector4(1, 1, 0, 3), draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + sa.DrawArrow(sid, 0, 3500, 10000, $"GEN_黑球路径{sid}", 0, 1f, 5.5f, new Vector4(0, 0, 1, 1)); + } + + [ScriptMethod(name: "GEN_拘束器内黑球爆炸范围", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"], + userControl: Debugging)] + public void GEN_拘束器内黑球爆炸范围(Event ev, ScriptAccessory sa) + { + for (int i = 0; i < _upm.拘束器坐标.Count; i++) + { + var destroyMs = _upm.当前阶段.GetDecimalDigit(3) == 3 ? 3500 : 10000; + sa.DrawCircle(_upm.拘束器坐标[i], 3500, destroyMs, $"GEN_拘束器内黑球爆炸范围{i}", 8f, new Vector4(1, 1, 0, 0.4f)); + } + } + + [ScriptMethod(name: "GEN_黑球爆炸范围删除", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9903", "TargetIndex:1"], + userControl: Debugging)] + public void GEN_黑球爆炸范围删除(Event ev, ScriptAccessory sa) + { + var sid = ev.SourceId; + var tid = ev.TargetId; + var spos = ev.SourcePosition; + + int minIdx = _upm.获得最近拘束器序列(spos); + sa.Method.RemoveDraw($"GEN_黑球路径{sid}.*"); + sa.Method.RemoveDraw($".*_{_upm.当前阶段}_黑球搭档连线.*"); + if (_upm.当前阶段 != 3500) + sa.Method.RemoveDraw($"GEN_拘束器内黑球爆炸范围{minIdx}.*"); + + var tidx = sa.GetPlayerIdIndex((uint)tid); + if (!sa.IsValidPartyIndex(tidx)) return; + _pd[tidx] = 0; + sa.Method.RemoveDraw($".*_{_upm.当前阶段}_黑球指路{tidx}.*"); + } + + [ScriptMethod(name: "GEN_删除旋风绘图", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9898)$", "TargetIndex:1"], + userControl: Debugging)] + public void GEN_删除旋风绘图(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw(@"GEN_\d{4}_旋风[0-9].*"); + } + + [ScriptMethod(name: "GEN_删除垂直下落绘图", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9896)$", "TargetIndex:1"], + userControl: Debugging)] + public void GEN_删除垂直下落绘图(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw(@"GEN_\d{4}_垂直下落.*"); + } + + [ScriptMethod(name: "GEN_删除液体地狱绘图", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9901)$", "TargetIndex:1"], + userControl: Debugging)] + public void GEN_删除液体地狱绘图(Event ev, ScriptAccessory sa) + { + _upm.液体地狱判定次数++; + sa.DebugMsg($"液体地狱判定次数 {_upm.液体地狱判定次数}"); + if (_upm.液体地狱判定次数 < 5) return; + _upm.液体地狱判定次数 = 0; + sa.Method.RemoveDraw(@"GEN_\d{4}_液体地狱.*"); + } + + [ScriptMethod(name: "GEN_黑球点名记录", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0076"], + userControl: Debugging)] + public void GEN_黑球点名记录(Event ev, ScriptAccessory sa) + { + lock (_stateLock) + { + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tidx)) return; + _pd.AddPriority(tidx, 100); + } + } + + [ScriptMethod(name: "GEN_火球分摊范围", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0075"], + userControl: true)] + public void GEN_火球分摊范围(Event ev, ScriptAccessory sa) + { + sa.DrawCircle(ev.TargetId, 0, 20000, $"GEN_双塔火球分摊范围", 4f, new Vector4(0.3f, 1, 0.3f, 1)); + if (_upm.当前阶段 < 3000) return; + sa.TextInfo("火球分摊"); + sa.TTS("火球分摊"); + if (!SpecialMode) return; + sa.DrawLockOn(ev.TargetId, 383, 100, 5000, new Vector3(2, 2, 2)); + } + + [ScriptMethod(name: "GEN_火球分摊范围删除", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9900", "TargetIndex:1"], + userControl: Debugging)] + public void GEN_火球分摊范围删除(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"GEN_双塔火球分摊范围"); + } + + [ScriptMethod(name: "GEN_液体地狱随机火圈范围", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9901)$", "TargetIndex:1"], + userControl: true)] + public async void GEN_液体地狱随机火圈范围(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段.GetDecimalDigit(3) == 3) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.当前阶段 >= 1999 || + (_upm.P1.技能循环轴[_upm.P1.技能序号] is TwinTaniaSkills.液体地狱 or TwinTaniaSkills.液体地狱随机), + () => _upm.液体地狱判定次数 == 1 + ])) return; + + var color = new Vector4(0.3f, 0.7f, 1, 1.5f); + sa.DrawCircle(ev.TargetId, 0, 10000, $"GEN_{_upm.当前阶段}_液体地狱随机火圈范围", 5f, color); + } + + private async void 液体地狱引导范围绘图(ScriptAccessory sa, bool phaseKeep) + { + if (_upm.当前阶段 < 1999) + { + // 加一层当前阶段判断保护 + var lastPhase = _upm.当前阶段; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P1.技能循环轴[_upm.P1.技能序号] == TwinTaniaSkills.液体地狱, + ])) return; + if (phaseKeep && _upm.当前阶段 != lastPhase) return; + } + + var color = new Vector4(0.3f, 0.3f, 1, 4f); + sa.DrawDonut(_upm.P1.双塔尼亚_ObjId, 0, 20000, $"GEN_{_upm.当前阶段}_液体地狱引导范围", 16.5f, 15f, color); + + var biasRole = _upm.当前阶段 == 4000 ? 6 : 3; + var ttsStr = sa.GetMyIndex() != biasRole ? "环内躲避" : "环外引导"; + sa.TextInfo($"{ttsStr} 液体地狱", destroyMs: 1500); + sa.TTS(ttsStr); + + // 液体地狱预警 + var color2 = new Vector4(0.3f, 0.7f, 1, 1.5f); + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (sa.GetById(sa.Data.PartyList[i]) is not { } obj) continue; + if (sa.GetById(_upm.P1.双塔尼亚_ObjId) is not { } bossObj) continue; + + var draw = sa.DrawCircle(sa.Data.PartyList[i], 0, 20000, + $"GEN_{_upm.当前阶段}_液体地狱预警{i}", 6f, color2, draw: false); + + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, draw, dp => + { + var distance = Vector3.Distance(obj.Position, bossObj.Position); + dp.Scale = distance > 15 ? new Vector2(5) : new Vector2(0); + }); + } + } + + #endregion 通用 双塔尼亚 + + #region 通用 奈尔 + + [ScriptMethod(name: "=============《通用 奈尔》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 奈尔_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "GEN_超新星(黑泥)危险范围", + eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2003393", "Operate:Add"], + userControl: true)] + public void GEN_超新星危险范围(Event ev, ScriptAccessory sa) + { + sa.DrawCircle(ev.SourcePosition, 0, 15000, $"GEN_超新星危险位置", 5f, new Vector4(1, 0, 0, 3)); + } + + [ScriptMethod(name: "GEN_台词连续技范围删除", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(992[01]|991[5678])$", "TargetIndex:1"], + userControl: Debugging)] + public void GEN_台词连续技范围删除(Event ev, ScriptAccessory sa) + { + var aid = ev.ActionId; + var tidx = -1; + if (aid == 9920) + { + tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tidx)) return; + } + var skillStr = aid switch + { + 9915 => "钢铁", + 9916 => "月环", + 9917 => "分摊", + 9918 => "凶鸟冲", + 9920 => $"陨石流{tidx}", + 9921 => "月华冲", + _ => "" + }; + if (skillStr == "") return; + sa.Method.RemoveDraw(@$"GEN_\d{{4}}_台词{skillStr}"); + } + + [ScriptMethod(name: "GEN_小龙俯冲范围", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(993[12345])$"], + userControl: true)] + public void GEN_小龙俯冲范围(Event ev, ScriptAccessory sa) + { + var sid = ev.SourceId; + sa.DrawRect(ev.SourceId, 0, 0, 4000, $"GEN_{_upm.当前阶段}_小龙俯冲范围_{sid}", + 0, 20, 60, sa.Data.DefaultDangerColor.WithW(1.5f), true); + + if (_upm.当前阶段 != 2010) return; + sa.Method.RemoveDraw($"P2C_{_upm.当前阶段}_小龙俯冲引导时范围_{sid}"); + sa.Method.RemoveDraw($"P2C_{_upm.当前阶段}_小龙俯冲引导位置指路_{sid}"); + } + + #endregion 通用 奈尔 + + #region P1 + + [ScriptMethod(name: "———————— 《P1》 ————————", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P1_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P1_记录双塔尼亚ID", + eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:627"], + userControl: Debugging)] + public void P1_记录双塔尼亚ID(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + + if (sa.GetById(ev.TargetId) is not { } obj) return; + if (obj.DataId != 8159) return; + + _upm.P1.双塔尼亚_ObjId = ev.TargetId; + } + + [ScriptMethod(name: "P1_双塔透明化且显示中心", + eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:627"], + userControl: true)] + public void P1_双塔透明化且显示中心(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + + if (sa.GetById(ev.TargetId) is not { } obj) return; + if (obj.DataId != 8159) return; + + sa.AlphaModify(obj, 0.5f); + // 不能被转阶段的删除绘图删掉 + sa.DrawCircle(_upm.P1.双塔尼亚_ObjId, 0, Int32.MaxValue, + $"P1_9999_双塔尼亚中心点_内圆", 0.4f, new Vector4(1, 0, 0, 2), useImgui: true); + sa.DrawDonut(_upm.P1.双塔尼亚_ObjId, 0, Int32.MaxValue, + $"P1_9999_双塔尼亚中心点_外环", 0.5f, 0.4f, new Vector4(0, 1, 1, 1), useImgui: true); + } + + [ScriptMethod(name: "P1_循环技能判定", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(989[678]|990[12])$", "TargetIndex:1"], + userControl: Debugging)] + public void P1_循环技能判定(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + if (ev.ActionId == 9901) + { + // if (!await WaitUntilConditions([() => _upm.P1.液体地狱循环轴判断次数 != _upm.液体地狱判定次数])) return; + _upm.P1.液体地狱循环轴判断次数++; + sa.DebugMsg($"液体地狱循环轴判断次数改变 {_upm.P1.液体地狱循环轴判断次数}"); + if (_upm.P1.液体地狱循环轴判断次数 < 5) return; + _upm.P1.液体地狱循环轴判断次数 = 0; + } + _upm.P1.增加循环技能序号(); + sa.DebugMsg($"当前循环技能序号:{_upm.P1.技能序号} 阶段 {_upm.当前阶段}"); + } + + private async void 垂直下落绘图(ScriptAccessory sa) + { + if (_upm.当前阶段 < 1999) + { + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P1.技能循环轴[_upm.P1.技能序号] == TwinTaniaSkills.垂直下落, + ])) return; + } + + if (sa.GetById(_upm.P1.双塔尼亚_ObjId) is not { } obj) return; + if (obj.DataId != 8159) return; + + sa.TextInfo("即将垂直下落(顺劈)", destroyMs: 1500); + sa.TTS("即将顺劈"); + + var dp = sa.DrawFan(_upm.P1.双塔尼亚_ObjId, 0, 10000, $"GEN_{_upm.当前阶段}_垂直下落范围", 90f.DegToRad(), 0, 11.96f, 0, + sa.Data.DefaultDangerColor.WithW(1.5f), draw: false); + dp.SetOwnerTarget(false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "P1_垂直下落范围(开场)", + eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:627"], + userControl: true)] + public void P1_垂直下落范围开场(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + + if (sa.GetById(ev.TargetId) is not { } obj) return; + if (obj.DataId != 8159) return; + 垂直下落绘图(sa); + } + + [ScriptMethod(name: "P1_垂直下落范围(中段)", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9898|9897)$", "TargetIndex:1"], + userControl: true)] + public void P1_垂直下落范围中段(Event ev, ScriptAccessory sa) + { + // 垂直下落只会在死刑后,或旋风后 + if (_upm.当前阶段 > 1999) return; + 垂直下落绘图(sa); + } + + [ScriptMethod(name: "P1_液体地狱引导范围(转阶段)", + eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["SourceDataId:8159", "Id:148"], + userControl: true)] + public void P1_液体地狱引导范围转阶段(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + 液体地狱引导范围绘图(sa, phaseKeep: false); + } + + [ScriptMethod(name: "P1_液体地狱引导范围(中段)", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9902|9896)$", "TargetIndex:1"], + userControl: true)] + public void P1_液体地狱引导范围中段(Event ev, ScriptAccessory sa) + { + // 液体地狱只会在阶段开场,或黑球后,或垂直下落后 + if (_upm.当前阶段 > 1999) return; + 液体地狱引导范围绘图(sa, phaseKeep: true); + } + + + [ScriptMethod(name: "P1_拘束器位置记录", + eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2001151", "Operate:Add"], + userControl: Debugging)] + public void P1_拘束器位置记录(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 2001) return; + + var tPos = ev.SourcePosition; + _upm.拘束器坐标.Add(tPos); + sa.DebugMsg($"{tPos.ToStr()}"); + + // 基于国服打法,收集完三个拘束器后,进行排序,B -> C -> D + if (_upm.拘束器坐标.Count != 3) return; + + _upm.拘束器坐标 = _upm.拘束器坐标 + .OrderBy(pos => pos.GetRadian(Center).RadianToRegion(3, 1, true, true)) + .ToList(); + sa.DebugMsg($"排序:{string.Join(", ", _upm.拘束器坐标.Select(p => p.ToStr()))}"); + _upm.求解拉怪位置(); + } + + [ScriptMethod(name: "P1_黑球搭档连线", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0076"], + userControl: true, suppress: 500)] + public async void P1_黑球搭档连线(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + var cnt = _upm.拘束器坐标.Count; + if (cnt == 1) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.SelectSpecificPriorityIndex(cnt - 1, true).Value >= 100, + ])) return; + + if (!Debugging && _pd[sa.GetMyIndex()] < 100) return; + var player1Idx = _pd.SelectSpecificPriorityIndex(0, true).Key; + var player2Idx = _pd.SelectSpecificPriorityIndex(1, true).Key; + sa.DrawConnection(sa.Data.PartyList[player1Idx], sa.Data.PartyList[player2Idx], 0, 20000, + $"P1_{_upm.当前阶段}_黑球搭档连线", new Vector4(1, 1, 0, 1)); + } + + + [ScriptMethod(name: "P1_黑球指路", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0076"], + userControl: true, suppress: 500)] + public async void P1_黑球指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + var cnt = _upm.拘束器坐标.Count; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.SelectSpecificPriorityIndex(cnt - 1, true).Value >= 100, + ])) return; + + // 将黑球点名玩家添加进 members + List members = []; + for (int i = 0; i < cnt; i++) + members.Add(_pd.SelectSpecificPriorityIndex(i, true)); + + // P1 采用就近原则指路 + for (int i = 0; i < members.Count; i++) + { + if (!Debugging && members[i].Key != sa.GetMyIndex()) continue; + var draw = sa.DrawGuidance(sa.Data.PartyList[members[i].Key], _upm.拘束器坐标[0], 0, 20000, + $"P1_{_upm.当前阶段}_黑球指路{members[i].Key}", sa.Data.DefaultSafeColor, draw: false); + + var i1 = i; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, draw, dp => + { + if (members.Count == 1) + dp.TargetPosition = _upm.拘束器坐标[0]; + else + { + if (sa.GetById(sa.Data.PartyList[members[i1 == 0 ? 1 : 0].Key]) is not { } partnerObj) return; + var partnerPos = partnerObj.Position; + + if (sa.GetById(sa.Data.PartyList[members[i1].Key]) is not { } myObj) return; + var myPos = myObj.Position; + + var myDistanceDelta = + Vector3.Distance(myPos, _upm.拘束器坐标[0]) - Vector3.Distance(myPos, _upm.拘束器坐标[1]); + var partnerDistanceDelta = + Vector3.Distance(partnerPos, _upm.拘束器坐标[0]) - Vector3.Distance(partnerPos, _upm.拘束器坐标[1]); + dp.TargetPosition = myDistanceDelta > partnerDistanceDelta ? _upm.拘束器坐标[1] : _upm.拘束器坐标[0]; + } + }); + } + } + + [ScriptMethod(name: "P1_根据双塔动作转阶段", + eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["SourceDataId:8159", "Id:148"], + userControl: Debugging)] + public void P1_根据双塔动作转阶段(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 1999) return; + sa.Method.RemoveDraw($".*_{_upm.当前阶段}.*"); + + _upm.当前阶段 = _upm.当前阶段 switch + { + 1100 => 1200, + 1200 => 2000, + _ => 1100, + }; + + if (_upm.当前阶段 != 2000) + { + _upm.P1.技能序号 = 0; + _upm.P1.获得阶段技能循环轴(_upm.当前阶段); + } + else + { + if (sa.GetById(ev.SourceId) is not { } obj) return; + if (obj.DataId != 8159) return; + // sa.AlphaModify(obj, 1f, currentAlpha => currentAlpha <= 0.6f); + sa.Method.RemoveDraw("GEN.*"); + sa.Method.RemoveDraw("P1.*"); + } + sa.DebugMsg($"{_upm.当前阶段}"); + } + #endregion P1 + + #region P2 + + [ScriptMethod(name: "———————— 《P2》 ————————", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2_分割线(Event ev, ScriptAccessory sa) + { + } + + #region P2A 开场阶段 2000~2002 + + [ScriptMethod(name: "=============《P2A 开场阶段》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2A_开场阶段_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2A_指向击退位置", + eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["SourceDataId:8159", "Id:148"], + userControl: true, suppress: 500)] + public async void P2A_指向击退位置(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 < 1200) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.当前阶段 == 2000, + ])) return; + + sa.DrawGuidance(new Vector3(0, 0, -9.37f), 0, 10000, $"P2A_{_upm.当前阶段}_指向击退位置", sa.Data.DefaultSafeColor); + sa.DrawKnockBack(Center, 0, 10000, $"P2_击退范围", 1f, 10f, sa.Data.DefaultDangerColor.WithW(1.5f)); + } + + [ScriptMethod(name: "P2A_诸神黄昏即死区高亮", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9912"], + userControl: true, suppress: 500)] + public void P2A_诸神黄昏即死区高亮(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2000) return; + var color = new Vector4(1, 0.2f, 0.2f, 3f); + if (sa.GetById(ev.SourceId) is not { } obj) return; + var dp = sa.DrawRect(obj.Position, 0, 20000, $"P2A_{_upm.当前阶段}_诸神黄昏即死区", 0, 9, 7, color, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + + [ScriptMethod(name: "P2A_开场陨石流分散", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9912"], + userControl: true, suppress: 500)] + public void P2A_开场陨石流分散(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2000) return; + var color = new Vector4(0.4f, 1, 1, 1.5f); + 执行台词连续技绘图(sa, NaelQuoteSkills.陨石流, 0, 20000, color); + } + + [ScriptMethod(name: "P2A_开场陨石流指路", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9912"], + userControl: true, suppress: 500)] + public void P2A_开场陨石流指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2000) return; + List<(int region, bool inside)> tPosList = + [ + (6, false), (8, true), (8, false), (10, false), + (4, true), (12, true), (4, false), (12, false) + ]; + + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (!Debugging && sa.GetMyIndex() != i) continue; + var tPos = new Vector3(0, 0, 20).RotateAndExtend(Center, 22.5f.DegToRad() * tPosList[i].region, + tPosList[i].inside ? -15 : 0); + sa.DrawGuidance(sa.Data.PartyList[i], tPos, 0, 10000, $"P2A_{_upm.当前阶段}_陨石流指路{i}", sa.Data.DefaultSafeColor); + } + } + + [ScriptMethod(name: "P2A_删除陨石流指路", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9920"], + userControl: Debugging, suppress: 500)] + public void P2A_删除陨石流指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2000) return; + sa.Method.RemoveDraw($"P2A_{_upm.当前阶段}_陨石流指路.*"); + } + + [ScriptMethod(name: "P2A_陨石流转阶段", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9920"], + userControl: Debugging, suppress: 500)] + public void P2A_陨石流转阶段(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 > 2002) return; + _upm.当前阶段 = _upm.当前阶段 switch + { + 2001 => 2002, + _ => 2001 + }; + sa.DebugMsg($"{_upm.当前阶段}"); + if (_upm.当前阶段 != 2002) return; + sa.Method.RemoveDraw($"P2A_2000.*"); + } + + [ScriptMethod(name: "P2A_月华冲(初始)", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9920"], + userControl: true, suppress: 500)] + public async void P2A_月华冲初始(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (2000 or 2001 or 2002)) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.当前阶段 == 2002, + ])) return; + sa.DrawCircle(sa.Data.PartyList[1], 0, 3000, $"GEN_{_upm.当前阶段}_台词月华冲", 5f, + sa.Data.DefaultDangerColor.WithW(1.5f)); + } + + #endregion P2A 开场阶段 2000~2002 + + #region P2B 龙神的加护 2010 + + [ScriptMethod(name: "=============《P2B 龙神的加护》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2B_龙神的加护_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2B_龙神的加护_转阶段", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9922"], + userControl: Debugging)] + public void P2B_龙神的加护_转阶段(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 2010; + _upm.P2.奈尔_ObjId = ev.SourceId; + sa.Method.RemoveDraw(@".*"); + sa.DebugMsg($"{_upm.当前阶段}"); + _pd.Init("P2死宣"); + } + + [ScriptMethod(name: "P2B_奈尔透明化", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9922"], + userControl: Debugging)] + public void P2B_奈尔透明化(Event ev, ScriptAccessory sa) + { + if (sa.GetById(ev.SourceId) is not { } obj) return; + sa.AlphaModify(obj, 0.5f); + } + + [ScriptMethod(name: "P2B_火龙连线分摊范围与指路", + eventType: EventTypeEnum.Tether, eventCondition: ["Id:0005"], + userControl: true)] + public void P2B_火龙连线分摊范围与指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + sa.DebugMsg($"上一轮受击玩家:{string.Join(", ", _upm.P2.烈火球受击玩家.Select(x => sa.GetPlayerJobByIndex(x)))}"); + + var myObj = sa.Data.MyObject; + if (myObj == null) return; + var tIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tIdx)) return; + + _upm.P2.烈火球轮数++; + sa.DebugMsg($"火龙连线 {sa.GetPlayerJobByIndex(tIdx)} 第 {_upm.P2.烈火球轮数} 轮"); + + switch (_upm.P2.烈火球轮数) + { + case 3: + sa.TextInfo("先雷,后火"); + sa.TTS("先雷,后火"); + break; + case 4: + sa.TextInfo("先火,后雷"); + sa.TTS("先火,后雷"); + break; + } + + var draw = sa.DrawCircle(ev.TargetId, 0, 5200, + $"P2B_{_upm.当前阶段}_火龙连线分摊范围", 4, sa.Data.DefaultSafeColor, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, draw, dp => + { + var myIndex = sa.GetMyIndex(); + var fireDanger = myObj.HasStatus(464) || + (_upm.P2.烈火球轮数 == 2 && tIdx != myIndex && _upm.P2.烈火球受击玩家.Contains(myIndex)) || + (_upm.P2.烈火球轮数 == 3 && tIdx != myIndex && _upm.P2.烈火球受击玩家.Contains(myIndex)); + dp.Color = (fireDanger ? sa.Data.DefaultDangerColor : sa.Data.DefaultSafeColor).WithW(1.5f); + }); + } + + [ScriptMethod(name: "P2B_火龙连线受击玩家刷新", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9925"], + userControl: Debugging, suppress: 500)] + public void P2B_火龙连线受击玩家刷新(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + _upm.P2.烈火球受击玩家.Clear(); + _upm.P2.烈火球受击玩家记录轮数 = _upm.P2.烈火球轮数; + } + + [ScriptMethod(name: "P2B_火龙连线受击玩家记录", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9925"], + userControl: Debugging)] + public async void P2B_火龙连线受击玩家记录(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P2.烈火球受击玩家记录轮数 == _upm.P2.烈火球轮数, + ])) return; + var tIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tIdx)) return; + _upm.P2.烈火球受击玩家.Add(tIdx); + } + + [ScriptMethod(name: "P2B_火龙连线分摊范围与指路删除", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9925", "TargetIndex:1"], + userControl: Debugging)] + public void P2B_火龙连线分摊范围删除(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + sa.Method.RemoveDraw(@"P2B_\d{4}_火龙连线分摊范围"); + sa.Method.RemoveDraw(@"P2B_\d{4}_火龙连线分摊指路"); + } + + [ScriptMethod(name: "P2B_雷点名范围", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9927"], + userControl: true)] + public void P2B_雷点名范围(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + var color = new Vector4(0.4f, 0.2f, 1f, 2f); + sa.DrawCircle(ev.TargetId, 0, 6000, $"P2B_雷点名范围", 5f, color); + // if (!Debugging && sa.GetPlayerIdIndex((uint)ev.TargetId) != sa.GetMyIndex()) return; + if (!SpecialMode) return; + sa.DrawLockOn(ev.TargetId, 507, 0, 6000, new(1.5f, 1.5f, 1.5f), 7f / 6f); + } + + [ScriptMethod(name: "P2B_雷点名范围删除", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9928"], + userControl: true)] + public void P2B_雷点名范围删除(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + sa.Method.RemoveDraw(@"P2B_雷点名范围"); + } + + private void 执行台词连续技绘图(ScriptAccessory sa, + NaelQuoteSkills nqs, int delayMs, int destroyMs, Vector4 color) + { + switch (nqs) + { + case NaelQuoteSkills.钢铁: + sa.DrawCircle(_upm.P2.奈尔_ObjId, delayMs, destroyMs, $"GEN_{_upm.当前阶段}_台词钢铁", 8.55f, color); + break; + case NaelQuoteSkills.月环: + sa.DrawDonut(_upm.P2.奈尔_ObjId, delayMs, destroyMs, $"GEN_{_upm.当前阶段}_台词月环", 22, 6, color); + break; + case NaelQuoteSkills.分摊: + sa.DrawCircle(Center, delayMs, destroyMs, $"GEN_{_upm.当前阶段}_台词分摊", 4, color); + if (!SpecialMode) break; + sa.DrawOmen(Center, 453, delayMs, destroyMs, new(4, 8, 4)); + break; + case NaelQuoteSkills.月华冲: + var dp = sa.DrawCircle(_upm.P2.奈尔_ObjId, delayMs, destroyMs, + $"GEN_{_upm.当前阶段}_台词月华冲", 5f, color, draw: false); + dp.SetEnmityOrder(true, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + break; + case NaelQuoteSkills.凶鸟冲: + for (int i = 0; i < sa.Data.PartyList.Count; i++) + sa.DrawCircle(sa.Data.PartyList[i], delayMs, destroyMs, + $"GEN_{_upm.当前阶段}_台词凶鸟冲{i}", 3f, color, byTime: true); + break; + case NaelQuoteSkills.陨石流: + for (int i = 0; i < sa.Data.PartyList.Count; i++) + sa.DrawCircle(sa.Data.PartyList[i], delayMs, destroyMs, + $"GEN_{_upm.当前阶段}_台词陨石流{i}", 4f, color); + break; + default: + break; + }; + } + + [ScriptMethod(name: "P2B_台词连续技", + eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(649[234567]|650[01])$"], + userControl: true)] + public async void P2B_台词连续技(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + var quoteId = ev.Id0(); + var color = new Vector4(0.4f, 1, 1, 1.5f); + switch (quoteId) + { + case 0x6492: + // 月光啊!照亮铁血霸道! + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.钢铁, 5000, 3000, color); + sa.TextInfo("月环 -> 钢铁", isWarning: true); + sa.TTS("月环,然后钢铁"); + break; + case 0x6493: + // 月光啊!用你的炽热烧尽敌人! + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.分摊, 5000, 3000, sa.Data.DefaultSafeColor); + sa.TextInfo("月环 -> 分摊", isWarning: true); + sa.TTS("月环,然后分摊"); + break; + case 0x6494: + // 被炽热灼烧过的轨迹,乃成铁血霸道! + 执行台词连续技绘图(sa, NaelQuoteSkills.分摊, 0, 5000, sa.Data.DefaultSafeColor); + 执行台词连续技绘图(sa, NaelQuoteSkills.钢铁, 5000, 3000, color); + sa.TextInfo("分摊 -> 钢铁", isWarning: true); + sa.TTS("分摊,然后钢铁"); + break; + case 0x6495: + // 炽热燃烧!给予我月亮的祝福! + 执行台词连续技绘图(sa, NaelQuoteSkills.分摊, 0, 5000, sa.Data.DefaultSafeColor); + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 5000, 3000, color); + sa.TextInfo("分摊 -> 月环", isWarning: true); + sa.TTS("分摊,然后月环"); + break; + case 0x6496: + // 我降临于此,征战铁血霸道! + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.钢铁, 5000, 3000, color); + sa.TextInfo("分散 -> 钢铁", isWarning: true); + sa.TTS("分散,然后钢铁"); + break; + case 0x6497: + // 我降临于此,对月长啸! + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 5000, 3000, color); + sa.TextInfo("分散 -> 月环", isWarning: true); + sa.TTS("分散,然后月环"); + break; + case 0x6500: + // 超新星啊,更加闪耀吧!在星降之夜,称赞红月! + 执行台词连续技绘图(sa, NaelQuoteSkills.陨石流, 12000, 3000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.月华冲, 15000, 2000, color); + var runId1 = _runId; + var phase1 = _upm.当前阶段; + await Task.Delay(8000); + if (_runId != runId1 || _upm.当前阶段 != phase1) return; + sa.TextInfo("保持分散", isWarning: true); + sa.TTS("保持分散"); + break; + case 0x6501: + // 超新星啊,更加闪耀吧!照亮红月下炽热之地! + 执行台词连续技绘图(sa, NaelQuoteSkills.月华冲, 13000, 3000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.分摊, 15000, 2000, sa.Data.DefaultSafeColor); + var runId2 = _runId; + var phase2 = _upm.当前阶段; + await Task.Delay(8000); + if (_runId != runId2 || _upm.当前阶段 != phase2) return; + sa.TextInfo("奈尔上天后,当前T分散,人群分摊", isWarning: true); + sa.TTS("当前T分散,人群分摊"); + break; + } + } + + [ScriptMethod(name: "P2B_死宣记录", + eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:210"], + userControl: Debugging)] + public void P2B_死宣记录(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (ev.SourceId != 0xE0000000) return; + var idx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(idx)) return; + var time = ev.DurationMilliseconds(); + var priVal = time switch + { + > 15000 => 10, + > 9000 => 20, + _ => 30 + }; + _pd.AddPriority(idx, priVal); + sa.DebugMsg($"{sa.GetPlayerJobByIndex(idx)} 死宣{4 - priVal / 10}", order: 30 - priVal); + + if (priVal != 30) return; + _upm.P2.死宣一记录完毕 = true; + } + + [ScriptMethod(name: "P2B_救世之翼预指路", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9930"], + userControl: true)] + public async void P2B_救世之翼预指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P2.死宣一记录完毕, + ])) return; + + var castIdx = _upm.P2.救世之翼序号; + var targetEntry = _pd.SelectSpecificPriorityIndex(castIdx, true); + var tIdx = targetEntry.Key; + _upm.P2.救世之翼序号++; + + if (!Debugging && tIdx != sa.GetMyIndex()) return; + var tPos = ev.EffectPosition; + var dpName = $"P2B_{_upm.当前阶段}_{tIdx}_死宣_准备吃圈"; + sa.DrawGuidance(sa.Data.PartyList[tIdx], tPos, 0, 4500, dpName, sa.Data.DefaultDangerColor); + + } + + [ScriptMethod(name: "P2B_救世之翼指路", + eventType: EventTypeEnum.ObjectChanged, eventCondition: ["Operate:Add", "DataId:2003412"], + userControl: true)] + public async void P2B_救世之翼指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P2.死宣一记录完毕, + ])) return; + + var foodIdx = _upm.P2.贡品序号; + _upm.P2.贡品序号++; + var targetEntry = _pd.SelectSpecificPriorityIndex(foodIdx, true); + var tIdx = targetEntry.Key; + sa.Method.RemoveDraw($"P2B_{_upm.当前阶段}_{tIdx}_死宣_准备吃圈"); + + // 画范围 + var dpRangeName = $"P2B_{_upm.当前阶段}_{tIdx}_{ev.SourceId}_死宣_范围"; + if (tIdx != sa.GetMyIndex()) + sa.DrawCircle(ev.SourceId, 0, 4500, dpRangeName, 1.25f, new Vector4(1, 0, 0, 4)); + + // 画指路 + if (!Debugging && tIdx != sa.GetMyIndex()) return; + var tPos = ev.SourcePosition; + var dpGuideName = $"P2B_{_upm.当前阶段}_{tIdx}_{ev.SourceId}_死宣_去吃圈"; + sa.DrawGuidance(sa.Data.PartyList[tIdx], tPos, 0, 4500, dpGuideName, sa.Data.DefaultSafeColor); + } + + [ScriptMethod(name: "P2B_死宣解除删除相关绘图", + eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:210"], + userControl: Debugging)] + public void P2B_死宣解除删除相关绘图(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (ev.SourceId != 0xE0000000) return; + var tIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tIdx)) return; + var removeDrawName = @$"P2B_{_upm.当前阶段}_{tIdx}_\d+_死宣.*"; + sa.Method.RemoveDraw(removeDrawName); + + // 若此时场上再没有人有死宣 Buff,字典恢复 + foreach (var member in sa.Data.PartyList) + { + if (sa.GetById(member) is not { } obj) continue; + if (((IPlayerCharacter)obj).HasStatus(210)) return; + } + _pd.Init("P2死宣"); + _upm.P2.死宣参数重置(); + sa.DebugMsg($"死宣参数重置"); + } + + [ScriptMethod(name: "P2B_贡品消失删除相关绘图", + eventType: EventTypeEnum.ObjectChanged, eventCondition: ["Operate:Remove", "DataId:2003412"], + userControl: Debugging)] + public void P2B_贡品消失删除相关绘图(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + var sid = ev.SourceId; + var removeDrawName = @$"P2B_{_upm.当前阶段}_\d_{sid}_死宣.*"; + sa.Method.RemoveDraw(removeDrawName); + } + + #endregion P2B 龙神的加护 2010 + + #region P2C 小龙俯冲 2010 + + [ScriptMethod(name: "=============《P2C 小龙俯冲》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2C_小龙俯冲_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2C_小龙方位记录", + eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(816[34567])$"], + userControl: Debugging)] + public void P2C_小龙方位记录(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (2002 or 2010)) return; + lock (_stateLock) + { + var spos = ev.SourcePosition; + // 以A为0,顺时针增加 + var region = spos.GetRadian(Center).RadianToRegion(8, 4, isDiagDiv: true, isCw: true); + _upm.P2.小龙列表.Add(new OuterDragon { ObjectId = ev.SourceId, Region = region }); + + if (_upm.P2.小龙列表.Count < 5) return; + _upm.P2.小龙列表 = _upm.P2.小龙列表.OrderBy(x => x.Region).ToList(); + _upm.P2.获得小龙俯冲引导点(); + sa.DebugMsg($"小龙方位 {string.Join(", ", _upm.P2.小龙列表.Select(x => x.Region))}\n" + + $"引导点 {string.Join(", ", _upm.P2.小龙俯冲引导点.Select(x => x))}"); + } + } + + [ScriptMethod(name: "P2C_小龙俯冲序号增加", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0014"], + userControl: Debugging)] + public void P2C_小龙俯冲序号增加(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + var tIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tIdx)) return; + _upm.P2.小龙点名轮数++; + _upm.P2.小龙俯冲引导玩家.Add(tIdx); + sa.DebugMsg($"小龙点名第 {_upm.P2.小龙点名轮数} 轮点 {sa.GetPlayerJobByIndex(tIdx)}", order: 0); + } + + [ScriptMethod(name: "P2C_小龙俯冲返回提示", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(993[12345])$"], + userControl: true, suppress: 500)] + public async void P2C_小龙俯冲返回提示(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P2.小龙点名轮数 > _upm.P2.小龙返回已处理轮数, + ])) return; + + var round = _upm.P2.小龙返回已处理轮数 + 1; + if (round > _upm.P2.小龙点名轮数) return; + _upm.P2.小龙返回已处理轮数 = round; + + var tIdx = _upm.P2.小龙俯冲引导玩家[round - 1]; + if (tIdx != sa.GetMyIndex()) return; + sa.TextInfo("快回去!", destroyMs: 2000, isWarning: true); + sa.TTS("快回去"); + } + + [ScriptMethod(name: "P2C_小龙俯冲引导时范围", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0014"], + userControl: true)] + public async void P2C_小龙俯冲引导时范围(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P2.小龙点名轮数 > _upm.P2.小龙范围已处理轮数, + ])) return; + + var round = _upm.P2.小龙范围已处理轮数 + 1; + if (round > _upm.P2.小龙点名轮数) return; + _upm.P2.小龙范围已处理轮数 = round; + + var tid = ev.TargetId; + if (!Debugging && tid != sa.Data.Me) return; + + var color = new Vector4(0.4f, 1, 1, 0.5f); + switch (round) + { + case 1: + sa.DrawRect(_upm.P2.小龙列表[0].ObjectId, tid, + 0, 7300, $"P2C_{_upm.当前阶段}_小龙俯冲引导时范围_{_upm.P2.小龙列表[0].ObjectId}", 0, 20, 45, color); + sa.DrawRect(_upm.P2.小龙列表[1].ObjectId, tid, + 0, 7300, $"P2C_{_upm.当前阶段}_小龙俯冲引导时范围_{_upm.P2.小龙列表[1].ObjectId}", 0, 20, 45, color); + break; + case 2: + sa.DrawRect(_upm.P2.小龙列表[2].ObjectId, tid, + 0, 7300, $"P2C_{_upm.当前阶段}_小龙俯冲引导时范围_{_upm.P2.小龙列表[2].ObjectId}", 0, 20, 45, color); + break; + case 3: + sa.DrawRect(_upm.P2.小龙列表[3].ObjectId, tid, + 0, 7300, $"P2C_{_upm.当前阶段}_小龙俯冲引导时范围_{_upm.P2.小龙列表[3].ObjectId}", 0, 20, 45, color); + sa.DrawRect(_upm.P2.小龙列表[4].ObjectId, tid, + 0, 7300, $"P2C_{_upm.当前阶段}_小龙俯冲引导时范围_{_upm.P2.小龙列表[4].ObjectId}", 0, 20, 45, color); + break; + default: + return; + } + } + + [ScriptMethod(name: "P2C_小龙俯冲引导位置指路", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0014"], + userControl: true)] + public async void P2C_小龙俯冲引导位置指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P2.小龙点名轮数 > _upm.P2.小龙指路已处理轮数, + ])) return; + + var round = _upm.P2.小龙指路已处理轮数 + 1; + if (round > _upm.P2.小龙点名轮数) return; + _upm.P2.小龙指路已处理轮数 = round; + + var tid = ev.TargetId; + if (!Debugging && tid != sa.Data.Me) return; + + var guideIndex = round - 1; + if (guideIndex < 0 || guideIndex >= _upm.P2.小龙俯冲引导点.Count) return; + var guideRegion = _upm.P2.小龙俯冲引导点[guideIndex]; + + sa.DebugMsg($"{sa.GetPlayerJobById((uint)tid)} 去方位 {guideRegion} 引导第 {round} 轮", order: 1); + var tPos = new Vector3(0, 0, -20).RotateAndExtend(Center, -30f.DegToRad() * guideRegion); + // 利用 ObjId 方便删除指路,正好 [1, 2, 3] 是三轮中的其中一条。 + var dragonObjId = _upm.P2.小龙列表[round].ObjectId; + sa.DrawGuidance(tid, tPos, 0, 6000, $"P2C_{_upm.当前阶段}_小龙俯冲引导位置指路_{dragonObjId}", sa.Data.DefaultSafeColor); + if (!SpecialMode) return; + sa.DrawCountDown(tPos, 2300, objIdBias: (uint)round); + } + + #endregion P2C 小龙俯冲 2010 + + #region P2D 第七灵灾 2020 + + [ScriptMethod(name: "=============《P2D 第七灵灾》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2D_第七灵灾_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2D_转阶段", + eventType: EventTypeEnum.Targetable, eventCondition: ["DataId:8161", "Targetable:False"], + // eventType: EventTypeEnum.Director, eventCondition: ["Command:80000001", "Instance:80037569"], + userControl: Debugging)] + public void P2D_转阶段(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 2010) return; + _upm.当前阶段 = 2020; + sa.Method.RemoveDraw($"P2[A-Z]_2010_.*"); + sa.Method.RemoveDraw($"GEN.*"); + sa.DebugMsg($"{_upm.当前阶段}"); + + if (sa.GetById(_upm.P2.奈尔_ObjId) is not { } obj) return; + // sa.AlphaModify(obj, 1f, currentAlpha => currentAlpha <= 0.6f); + } + + [ScriptMethod(name: "P2D_指向击退位置", + eventType: EventTypeEnum.Director, eventCondition: ["Command:80000001", "Instance:80037569"], + userControl: true)] + public async void P2D_指向击退位置(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (2010 or 2020)) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.当前阶段 == 2020, + ])) return; + + sa.DrawGuidance(new Vector3(0, 0, -9.37f), 0, 10000, $"P2D_{_upm.当前阶段}_指向击退位置", sa.Data.DefaultSafeColor); + sa.DrawKnockBack(Center, 0, 10000, $"P2D_{_upm.当前阶段}_击退范围", 1f, 10f, sa.Data.DefaultDangerColor.WithW(1.5f)); + } + + [ScriptMethod(name: "P2D_第七灵灾删除绘图与转阶段", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(993[79])$"], + userControl: Debugging, suppress: 500)] + public void P2D_第七灵灾删除绘图(Event ev, ScriptAccessory sa) + { + var aid = ev.ActionId; + sa.Method.RemoveDraw($".*"); + + // 最后一段伤害 灵灾之焰 9939 + if (aid != 9939) return; + _upm.当前阶段 = 3000; + sa.DebugMsg($"{_upm.当前阶段}"); + } + + #endregion P2D 第七灵灾 2020 + + #endregion P2 + + #region P3 + + [ScriptMethod(name: "———————— 《P3》 ————————", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3_巴哈透明化", + eventType: EventTypeEnum.Targetable, eventCondition: ["Targetable:True", "DataId:8168"], + userControl: Debugging, suppress: 500)] + public void P3_巴哈透明化(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3000) return; + if (sa.GetById(ev.SourceId) is not { } obj) return; + sa.AlphaModify(obj, 0.5f); + } + + [ScriptMethod(name: "P3_记录巴哈姆特ID", + eventType: EventTypeEnum.Targetable, eventCondition: ["Targetable:True", "DataId:8168"], + userControl: Debugging, suppress: 500)] + public void P3A_记录巴哈姆特ID(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3000) return; + if (_upm.P3.巴哈_ObjId != 0) return; + _upm.P3.巴哈_ObjId = ev.SourceId; + _upm.P3.获得阶段技能循环轴(_upm.当前阶段); + } + + [ScriptMethod(name: "P3_记录奈尔ID", + eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7747", "SourceDataId:8161"], + userControl: Debugging)] + public void P3_记录奈尔ID(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (3000 or 3010)) return; + if (_upm.P3.奈尔_ObjId != 0) return; + _upm.P3.奈尔_ObjId = ev.SourceId; + } + + [ScriptMethod(name: "P3_记录双塔尼亚ID", + eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7748", "SourceDataId:8159"], + userControl: Debugging)] + public void P3_记录双塔尼亚ID(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (3000 or 3010)) return; + if (_upm.P3.双塔_ObjId != 0) return; + _upm.P3.双塔_ObjId = ev.SourceId; + } + + [ScriptMethod(name: "P3_记录Boss位置与方位", + eventType: EventTypeEnum.PlayActionTimeline, + eventCondition: ["SourceDataId:regex:^(8161|8159|8168)$", "Id:regex:^(774[78])$"], + userControl: Debugging)] + public void P3_记录Boss位置(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段.GetDecimalDigit(3) != 3) return; + var spos = ev.SourcePosition; + if (Vector3.Distance(spos, Center) < 18) return; + var sdid = ev.SourceDataId(); + switch (sdid) { + case 8161: + _upm.P3.奈尔_Pos = spos; + _upm.P3.奈尔方位 = spos.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + _upm.P3.奈尔记录阶段 = _upm.当前阶段; + sa.DebugMsg($"{_upm.当前阶段} 更新奈尔方位 {_upm.P3.奈尔方位} {_upm.P3.奈尔_Pos.ToStr()}"); + break; + case 8159: + _upm.P3.双塔_Pos = spos; + _upm.P3.双塔方位 = spos.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + _upm.P3.双塔记录阶段 = _upm.当前阶段; + sa.DebugMsg($"{_upm.当前阶段} 更新双塔方位 {_upm.P3.双塔方位} {_upm.P3.双塔_Pos.ToStr()}"); + break; + case 8168: + _upm.P3.巴哈_Pos = spos; + _upm.P3.巴哈方位 = spos.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + _upm.P3.巴哈记录阶段 = _upm.当前阶段; + sa.DebugMsg($"{_upm.当前阶段} 更新巴哈方位 {_upm.P3.巴哈方位} {_upm.P3.巴哈_Pos.ToStr()} "); + break; + } + } + + [ScriptMethod(name: "P3_巴哈本体技能判定", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(994[012])$", "TargetIndex:1"], + userControl: Debugging, suppress: 500)] + public void P3_巴哈本体技能判定(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段.GetDecimalDigit(3) != 3) return; + + var actionId = ev.ActionId; + switch (actionId) + { + case UcobParamsP3.吐息: + { + if (_upm.P3.技能循环轴[_upm.P3.技能序号] == BahamutSkills.三连吐息) + { + _upm.P3.三连吐息判定次数++; + sa.DebugMsg($"三连吐息判定次数 {_upm.P3.三连吐息判定次数}"); + if (_upm.P3.三连吐息判定次数 < 3) return; + _upm.P3.三连吐息判定次数 = 0; + } + sa.Method.RemoveDraw($"P3_{_upm.当前阶段}_吐息.*"); + break; + } + case UcobParamsP3.十亿核爆: + break; + case UcobParamsP3.夷为平地: + break; + } + _upm.P3.增加技能序号(); + sa.DebugMsg($"当前技能序号:{_upm.P3.技能序号} 阶段 {_upm.当前阶段}"); + } + + private async void P3_吐息范围绘图(ScriptAccessory sa) + { + try + { + if (!await WaitUntilConditions( + timeoutMs: 500, + conditions: + [ + () => _upm.P3.吐息已绘图技能序号 < _upm.P3.技能序号, + () => _upm.P3.技能循环轴[_upm.P3.技能序号] is BahamutSkills.吐息 or BahamutSkills.三连吐息, + () => _upm.P3.巴哈_ObjId != 0, + ])) return; + + _upm.P3.吐息已绘图技能序号 = _upm.P3.技能序号; + var color = new Vector4(0.3f, 1f, 1, 1f); + var dp = sa.DrawFan(_upm.P3.巴哈_ObjId, 0, 15000, + $"P3_{_upm.当前阶段}_吐息范围", 90f.DegToRad(), 0, 30f, 0, + color, draw: false); + dp.SetOwnerTarget(false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + catch (Exception e) + { + sa.DebugMsg($"出错 {e}"); + } + } + + [ScriptMethod(name: "P3_吐息范围(开场)", + eventType: EventTypeEnum.Targetable, eventCondition: ["Targetable:True", "DataId:8168"], + userControl: true)] + public void P3_吐息范围开场(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 >= 3100) return; + P3_吐息范围绘图(sa); + } + + [ScriptMethod(name: "P3_吐息范围(中段)", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(994[0124])$", "TargetIndex:1"], + userControl: true, suppress: 500)] + public void P3_吐息范围中段(Event ev, ScriptAccessory sa) + { + // 吐息只会在吐息、风暴之翼、死刑、十亿核爆后 + if (_upm.当前阶段 is > 3999 or < 3000) return; + P3_吐息范围绘图(sa); + } + + [ScriptMethod(name: "P3_旋风冲、月流冲、核爆冲", + eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(9906|9923|9953)$"], + userControl: true)] + public void P3_旋风冲月流冲核爆冲(Event ev, ScriptAccessory sa) + { + const uint 双塔旋风冲 = 9906; + const uint 奈尔月流冲 = 9923; + const uint 巴哈核爆冲 = 9953; + + var color = new Vector4(0.3f, 1f, 1, 1f); + + var dpName = ev.ActionId switch + { + 双塔旋风冲 => $"P3_{_upm.当前阶段}_双塔旋风冲", + 奈尔月流冲 => $"P3_{_upm.当前阶段}_奈尔月流冲", + 巴哈核爆冲 => $"P3_{_upm.当前阶段}_巴哈核爆冲", + _ => "" + }; + + sa.DrawRect(ev.SourceId, 0, 0, 4000, + dpName, 0, ev.ActionId == 巴哈核爆冲 ? 12 : 8, 45, color); + } + + [ScriptMethod(name: "P3_旋风冲、月流冲、核爆冲删除", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9906|9923|9953)$", "TargetIndex:1"], + userControl: Debugging)] + public void P3_旋风冲月流冲核爆冲删除(Event ev, ScriptAccessory sa) + { + const uint 双塔旋风冲 = 9906; + const uint 奈尔月流冲 = 9923; + const uint 巴哈核爆冲 = 9953; + + var dpName = ev.ActionId switch + { + 双塔旋风冲 => @"P3_\d{4}_双塔旋风冲", + 奈尔月流冲 => @"P3_\d{4}_奈尔月流冲", + 巴哈核爆冲 => @"P3_\d{4}_巴哈核爆冲", + _ => "" + }; + sa.Method.RemoveDraw(dpName); + } + + [ScriptMethod(name: "P3_接线加深", + eventType: EventTypeEnum.Tether, eventCondition: ["Id:0004"], + userControl: Debugging, suppress: 10000)] + public void P3_接线加深(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (3100 or 3300)) return; + var color = new Vector4(1f, 1f, 0.1f, 1f); + + // 第一根 + var draw1 = sa.DrawLine(ev.SourceId, ev.TargetId, 0, 6000, + $"P3_{_upm.当前阶段}_接线加深1", 0, 1f, 1f, color, byY: true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, draw1, dp => + { + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var member = sa.Data.PartyList[i]; + if (sa.GetById(member) is not { } obj) continue; + var tetherSourceList = sa.GetTetherSource((IBattleChara)obj, 0x0004); + if (tetherSourceList.Count == 0) continue; + if (tetherSourceList[0] != _upm.P3.巴哈_ObjId) continue; + dp.Owner = member; + break; + } + }); + + // 第二根 + var draw2 = sa.DrawLine(ev.SourceId, ev.TargetId, 0, 6000, + $"P3_{_upm.当前阶段}_接线加深2", 0, 1f, 1f, color, byY: true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, draw2, dp => + { + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var memberIndex = sa.Data.PartyList.Count - 1 - i; + var member = sa.Data.PartyList[memberIndex]; + if (sa.GetById(member) is not { } obj) continue; + var tetherSourceList = sa.GetTetherSource((IBattleChara)obj, 0x0004); + if (tetherSourceList.Count == 0) continue; + if (tetherSourceList[0] != _upm.P3.巴哈_ObjId) continue; + dp.Owner = member; + break; + } + }); + } + + [ScriptMethod(name: "P3_大地摇动范围", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"], + userControl: Debugging)] + public void P3_大地摇动范围(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (3100 or 3500)) return; + var color = new Vector4(0.7f, 0.7f, 0.3f, _upm.当前阶段 == 3500 ? 1f : 0.7f); + sa.DrawFan(Center, ev.TargetId, 0, 5000, + $"P3_{_upm.当前阶段}_大地摇动范围", 90f.DegToRad(), 0, 50f, 0, color); + } + + [ScriptMethod(name: "P3_删除以太失控屏幕特效", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9905"], + userControl: true, suppress: 500)] + public void P3_删除以太失控屏幕特效(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段.GetDecimalDigit(3) != 3) return; + if (sa.GetById(ev.SourceId) is not { } obj) return; + sa.Redraw(obj); + } + + [ScriptMethod(name: "P3_删除转阶段屏幕特效", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(995[456789])$"], + userControl: true, suppress: 500)] + public void P3_删除转阶段屏幕特效(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段.GetDecimalDigit(3) != 3) return; + if (sa.GetById(ev.SourceId) is not { } obj) return; + sa.Redraw(obj); + } + + [ScriptMethod(name: "P3_删除十亿核爆屏幕特效", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9942"], + userControl: true, suppress: 500)] + public void P3_删除十亿核爆屏幕特效(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段.GetDecimalDigit(3) != 3) return; + if (sa.GetById(ev.SourceId) is not { } obj) return; + sa.Redraw(obj); + } + + #region P3A 进军的三重奏 3100-3150 + + [ScriptMethod(name: "=============《P3A 进军的三重奏》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3A_进军的三重奏_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A_进军阶段转换", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9954"], + userControl: Debugging)] + public void P3A_进军阶段转换(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 3100; + _upm.P3.巴哈_ObjId = ev.SourceId; + sa.Method.RemoveDraw(".*"); + _pd.Init("P3进军"); + _pd.AddPriorities([1, 2, 3, 4, 5, 6, 7, 8]); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + [ScriptMethod(name: "P3A_百万核爆分散范围", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9953"], + userControl: true)] + public void P3A_百万核爆分散范围(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3100) return; + var color = new Vector4(0.3f, 1f, 1, 0.5f); + for (int i = 0; i < sa.Data.PartyList.Count; i++) + sa.DrawCircle(sa.Data.PartyList[i], 0, 4000, + $"P3A_{_upm.当前阶段}_百万核爆分散范围{i}", 5f, color, byTime: true); + sa.TextInfo("分散分散", destroyMs: 2000, isWarning: true); + sa.TTS("分散分散"); + } + + [ScriptMethod(name: "P3A_旋风八方指路", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9953"], + userControl: true)] + public async void P3A_旋风八方指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3100) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P3.巴哈记录阶段 == _upm.当前阶段 + ])) return; + + var baseRad = _upm.P3.巴哈方位 * 45f.DegToRad(); + var basePos = new Vector3(0, 0, 19.5f).RotateAndExtend(Center, baseRad); + List rotDeg = [48, -48, 76, -76, 104, -104, 132, -132]; + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var color = i switch + { + 0 or 1 => new Vector4(0.1f, 0.1f, 1, 1), + 2 or 3 => new Vector4(0.1f, 1f, 0.1f, 1), + _ => new Vector4(1, 0.1f, 0.1f, 1), + }; + sa.DrawLine(Center, 0, 0, 4000, $"P3A_{_upm.当前阶段}_旋风八方指路_指引线{i}", + baseRad + rotDeg[i].DegToRad(), 20f, 25f, color); + + if (!Debugging && sa.GetMyIndex() != i) continue; + var member = sa.Data.PartyList[i]; + sa.DrawGuidance(member, basePos.RotateAndExtend(Center, rotDeg[i].DegToRad()), + 0, 4000, $"P3A_{_upm.当前阶段}_旋风八方指路{i}", sa.Data.DefaultSafeColor); + } + } + + [ScriptMethod(name: "P3A_旋风后指引线", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9948"], + userControl: true, suppress: 500)] + public void P3A_旋风后指引线(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3100) return; + var baseRad = _upm.P3.巴哈方位 * 45f.DegToRad(); + List rotDeg = [0, 90f, -90f, 180f, 90f, -90f]; + + for (int i = 0; i < 6; i++) + { + var startPos = i is 1 or 2 + ? new Vector3(0, 0, 10).RotateAndExtend(Center, baseRad + rotDeg[i].DegToRad()) + : Center; + var length = i is 0 or 3 ? 25 : 10; + var dp = sa.DrawLine(startPos, 0, 0, 5000, $"P3A_{_upm.当前阶段}_旋风后指引线{i}", + baseRad + rotDeg[i].DegToRad(), 20f, length, sa.Data.DefaultSafeColor, draw: false); + dp.Color = i switch + { + 0 => new Vector4(1, 0.1f, 0.1f, 1), + 1 or 2 => new Vector4(0.1f, 1f, 0.1f, 1), + 3 => new Vector4(0.1f, 1f, 1f, 1), + 4 or 5 => new Vector4(0.1f, 0.1f, 1f, 1), + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + + var towerPos = new Vector3(0, 0, -6).RotateAndExtend(Center, baseRad); + if (!SpecialMode) return; + sa.DrawOmen(towerPos, 453, 0, 5000, new(4, 8, 4)); + } + + [ScriptMethod(name: "P3A_进军机制点名收集", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(002[78])$"], + userControl: Debugging)] + public void P3A_进军机制点名收集(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3100) return; + lock (_stateLock) + { + var iconId = ev.Id0(); + var priVal = iconId switch + { + 0x0027 => 10, // 分摊 + 0x0028 => 20, // 大地摇动 + _ => 0 + }; + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tidx)) return; + _pd.AddPriority(tidx, priVal); + _pd.AddActionCount(); + sa.DebugMsg($"{sa.GetPlayerJobByIndex(tidx)} 被点名 {(priVal == 10 ? "分摊" : "大地摇动")}", priVal + _pd.ActionCount); + if (_pd.ActionCount != 6) return; + sa.DebugMsg($"进军机制点名收集完毕", 30); + } + } + + [ScriptMethod(name: "P3A_旋风后指路", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(002[78])$"], + userControl: true, suppress: 500)] + public async void P3A_旋风后指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3100) return; + if (!await WaitUntilConditions( + timeoutMs: 4000, + conditions: + [ + () => _pd.ActionCount == 6, + ])) return; + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + // 降序排列 + // 0 1 2 大地摇动 北、东、西 + // 3 4 5 南分摊 + // 6 7 东、西接线 + var pdEntry = _pd.SelectSpecificPriorityIndex(i, true); + if (!Debugging && sa.GetMyIndex() != pdEntry.Key) continue; + var member = sa.Data.PartyList[pdEntry.Key]; + 处理进军指路(sa, member, i); + } + } + + private void 处理进军指路(ScriptAccessory sa, ulong member, int pdIndex) + { + switch (pdIndex) + { + case 0 or 1 or 2: + var rotRad1 = pdIndex switch + { + 0 => 0, + 1 => -90f.DegToRad(), + 2 => 90f.DegToRad() + }; + var tPos1 = new Vector3(0, 0, 20f).RotateAndExtend(Center, rotRad1 + _upm.P3.巴哈方位 * 45f.DegToRad()); + sa.DrawGuidance(member, tPos1, 0, 5000, $"P3A_{_upm.当前阶段}_进击指路{pdIndex}_大地摇动", sa.Data.DefaultSafeColor); + break; + case 3 or 4 or 5: + var tPos2 = new Vector3(0, 0, -6f).RotateAndExtend(Center, _upm.P3.巴哈方位 * 45f.DegToRad()); + sa.DrawGuidance(member, tPos2, 0, 5000, $"P3A_{_upm.当前阶段}_进击指路{pdIndex}_分摊", sa.Data.DefaultSafeColor); + break; + case 6 or 7: + var rotRad3 = pdIndex switch + { + 6 => -90f.DegToRad(), + 7 => 90f.DegToRad(), + }; + var tPos3 = new Vector3(0, 0, 5f).RotateAndExtend(Center, rotRad3 + _upm.P3.巴哈方位 * 45f.DegToRad()); + sa.DrawGuidance(member, tPos3, 0, 5000, $"P3A_{_upm.当前阶段}_进击指路{pdIndex}_接线", sa.Data.DefaultSafeColor); + break; + } + } + + [ScriptMethod(name: "P3A_接线死刑范围", + eventType: EventTypeEnum.Tether, eventCondition: ["Id:0004"], + userControl: Debugging, suppress: 10000)] + public void P3A_接线死刑范围(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3100) return; + var color = sa.Data.DefaultDangerColor; + + // 只有线在坦克身上才显示范围 + var draw1 = sa.DrawCircle(ev.TargetId, 0, 6000, + $"P3A_{_upm.当前阶段}_接线死刑范围1", 5f, color, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, draw1, dp => + { + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var member = sa.Data.PartyList[i]; + if (sa.GetById(member) is not { } obj) continue; + var tetherSourceList = sa.GetTetherSource((IBattleChara)obj, 0x0004); + if (tetherSourceList.Count == 0) continue; + if (tetherSourceList[0] != _upm.P3.巴哈_ObjId) continue; + dp.Owner = member; + dp.Color = dp.Color.WithW(i <= 1 ? 1 : 0); + break; + } + }); + + var draw2 = sa.DrawCircle(ev.TargetId, 0, 6000, + $"P3A_{_upm.当前阶段}_接线死刑范围2", 5f, color, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, draw2, dp => + { + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var memberIndex = sa.Data.PartyList.Count - 1 - i; + var member = sa.Data.PartyList[memberIndex]; + if (sa.GetById(member) is not { } obj) continue; + var tetherSourceList = sa.GetTetherSource((IBattleChara)obj, 0x0004); + if (tetherSourceList.Count == 0) continue; + if (tetherSourceList[0] != _upm.P3.巴哈_ObjId) continue; + dp.Owner = member; + dp.Color = dp.Color.WithW(memberIndex <= 1 ? 1 : 0); + break; + } + }); + } + + [ScriptMethod(name: "P3A_转阶段与刷新技能", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9944"], + userControl: Debugging, suppress: 500)] + public void P3A_转阶段与刷新技能(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3100) return; + sa.Method.RemoveDraw(@".*_3100.*"); + + _upm.当前阶段 = 3150; + _upm.P3.获得阶段技能循环轴(_upm.当前阶段); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + #endregion P3A 进军的三重奏 3100-3150 + + #region P3B 黑炎的三重奏 3200-3250 + + [ScriptMethod(name: "=============《P3B 黑炎的三重奏》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3B_黑炎的三重奏_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3B_黑炎阶段转换", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9955"], + userControl: Debugging)] + public void P3B_黑炎阶段转换(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 3200; + _pd.Init("P3黑炎"); + _pd.AddPriorities([1, 2, 3, 4, 8, 7, 6, 5]); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + [ScriptMethod(name: "P3B_黑炎指路准备", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9955"], + userControl: true)] + public void P3B_黑炎指路准备(Event ev, ScriptAccessory sa) + { + sa.DrawGuidance(Center, 0, 4000, $"P3B_{_upm.当前阶段}_黑炎指路场中", sa.Data.DefaultSafeColor); + if (!SpecialMode) return; + sa.DrawCountDown(Center, 3500, iconScale: 3f, objIdBias: 0); + sa.TextInfo("倒计时结束后,向奈尔移动", delayMs: 3500); + } + + [ScriptMethod(name: "P3B_移动方向指引", + eventType: EventTypeEnum.PlayActionTimeline, + eventCondition: ["SourceDataId:regex:^(8161)$", "Id:regex:^(7747)$"], + userControl: true)] + public async void P3B_移动方向指引(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3200) return; + var spos = ev.SourcePosition; + if (Vector3.Distance(spos, Center) < 18) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P3.奈尔记录阶段 == _upm.当前阶段, + ])) return; + + var myIndex = sa.GetMyIndex(); + var rad1 = _upm.P3.奈尔方位 * 45f.DegToRad(); + var rad2 = (_upm.P3.奈尔方位 * 2 + (myIndex <= 3 ? 15 : 1)) % 16 * 22.5f.DegToRad(); + + sa.DrawRect(Center, 0, 1000, $"P3B_{_upm.当前阶段}_移动方向指引1", + rad1, 2, 22, sa.Data.DefaultDangerColor.WithW(2f)); + sa.DrawFan(Center, 0, 1000, $"P3B_{_upm.当前阶段}_移动方向指引2", + 45f.DegToRad(), rad2, 22, 20, sa.Data.DefaultDangerColor.WithW(2f)); + + sa.DrawRect(Center, 1000, 7500, $"P3B_{_upm.当前阶段}_移动方向指引1", + rad1, 2, 22, sa.Data.DefaultSafeColor.WithW(2f)); + sa.DrawFan(Center, 1000, 7500, $"P3B_{_upm.当前阶段}_移动方向指引2", + 45f.DegToRad(), rad2, 22, 20, sa.Data.DefaultSafeColor.WithW(2f)); + } + + [ScriptMethod(name: "P3B_出发提示", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9901", "TargetIndex:1"], + userControl: true, suppress: 10000)] + public void P3B_出发提示(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3200) return; + sa.TextInfo("走走走", destroyMs: 2000, isWarning: true); + sa.TTS("走走走"); + } + + [ScriptMethod(name: "P3B_黑炎机制点名收集", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0027)$"], + userControl: Debugging)] + public void P3B_黑炎机制点名收集(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3200) return; + lock (_stateLock) + { + var iconId = ev.Id0(); + var priVal = iconId switch + { + 0x0027 => 10, // 分摊 + _ => 0 + }; + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tidx)) return; + _pd.AddPriority(tidx, priVal); + _pd.AddActionCount(); + sa.DebugMsg($"{sa.GetPlayerJobByIndex(tidx)} 被点名 分摊", priVal + _pd.ActionCount); + if (_pd.ActionCount != 4) return; + sa.DebugMsg($"黑炎机制点名收集完毕", 30); + } + } + + [ScriptMethod(name: "P3B_踩塔分摊指路", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0027)$"], + userControl: true, suppress: 2000)] + public async void P3B_踩塔分摊指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3200) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.ActionCount == 4, + ])) return; + + // 塔的方位固定,为奈尔方位 +1 +3 +5 +7,可根据优先级分配塔 + // 分摊位置固定,为奈尔方位 +4 + + var naelRegion = _upm.P3.奈尔方位; + int[] towerRegion = [ + (naelRegion + 7) % 8, (naelRegion + 5) % 8, (naelRegion + 3) % 8, (naelRegion + 1) % 8 + ]; + var stackPos = new Vector3(0, 0, -8.5f).RotateAndExtend(Center, naelRegion * 45f.DegToRad()); + + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (!Debugging && sa.GetMyIndex() != i) continue; + var member = sa.Data.PartyList[i]; + var priRank = _pd.FindPriorityIndexOfKey(i); + if (priRank <= 3) + { + var targetTowerPos = new Vector3(0, 0, 14).RotateAndExtend(Center, towerRegion[priRank] * 45f.DegToRad()); + sa.DrawGuidance(member, targetTowerPos, 0, 5200, + $"P3B_{_upm.当前阶段}_踩塔分摊指路{i}准备", sa.Data.DefaultDangerColor); + sa.DrawGuidance(member, targetTowerPos, 5200, 2000, + $"P3B_{_upm.当前阶段}_踩塔分摊指路{i}踩塔", sa.Data.DefaultSafeColor); + sa.DrawCircle(targetTowerPos, 0, 5200, + $"P3B_{_upm.当前阶段}_踩塔分摊指路{i}塔危险区", 5f, sa.Data.DefaultDangerColor.WithW(2f)); + + if (sa.GetMyIndex() == i) + { + sa.TextInfo(SpecialMode ? "倒计时结束后,再进塔" : "等待一次超新星,再进塔", destroyMs: 3200, isWarning: true); + sa.TTS("稍后进塔"); + } + if (SpecialMode) + sa.DrawCountDown(targetTowerPos, 200, objIdBias: (uint)i); + } + else + { + sa.DrawGuidance(member, stackPos, 0, 5000, $"P3B_{_upm.当前阶段}_踩塔分摊指路{i}", sa.Data.DefaultSafeColor); + if (!SpecialMode || sa.GetMyIndex() != i) continue; + sa.DrawOmen(stackPos, 453, 0, 5000, new(4, 8, 4)); + } + } + } + + [ScriptMethod(name: "P3B_转阶段与刷新技能", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9951"], + userControl: Debugging, suppress: 500)] + public void P3B_转阶段与刷新技能(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3200) return; + sa.Method.RemoveDraw(@".*_3200.*"); + + _upm.当前阶段 = 3250; + _upm.P3.获得阶段技能循环轴(_upm.当前阶段); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + + #endregion P3B 黑炎的三重奏 3200-3250 + + #region P3C 灾厄的三重奏 3300-3350 + + [ScriptMethod(name: "=============《P3C 灾厄的三重奏》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3C_灾厄的三重奏_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3C_灾厄阶段转换", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9956"], + userControl: Debugging)] + public void P3C_灾厄阶段转换(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 3300; + _upm.P3.灾厄台词计数 = 0; + sa.Method.RemoveDraw(".*"); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + [ScriptMethod(name: "P3C_台词连续技", + eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(650[23])$"], + userControl: true)] + public void P3C_台词连续技(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3300) return; + var quoteId = ev.Id0(); + var color = new Vector4(0.4f, 1, 1, 1.5f); + switch (quoteId) + { + case 0x6502: + // 我降临于此对月长啸!召唤星降之夜! + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 5000, 3000, color); + sa.TextInfo("分散 -> 月环", isWarning: true); + sa.TTS("分散,然后月环"); + break; + case 0x6503: + // 我自月而来降临于此,召唤星降之夜! + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 5000, 3000, color); + sa.TextInfo("月环 -> 分散", isWarning: true); + sa.TTS("月环,然后分散"); + break; + } + } + + [ScriptMethod(name: "P3C_获得拘束器序列", + eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(650[23])$"], + userControl: Debugging)] + public void P3C_获得拘束器序列(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3300) return; + var peopleIdx = _upm.获得最近拘束器序列(ev.SourcePosition); + _upm.P3.灾厄对应拘束器[2] = peopleIdx; + _upm.P3.灾厄对应拘束器[0] = (peopleIdx + 1) % 3; + _upm.P3.灾厄对应拘束器[1] = (peopleIdx + 2) % 3; + sa.DebugMsg($"MT {_upm.P3.灾厄对应拘束器[0]}, ST {_upm.P3.灾厄对应拘束器[1]}, 人群 {_upm.P3.灾厄对应拘束器[2]}"); + } + + [ScriptMethod(name: "P3C_进拘束器指路", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(9918|9916)$", "TargetIndex:1"], + userControl: true)] + public void P3C_进拘束器指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3300) return; + _upm.P3.灾厄台词计数++; + + if (ev.ActionId == 9916) + { + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (i >= 2) continue; + if (!Debugging && sa.GetMyIndex() != i) continue; + var bhIdx = _upm.P3.灾厄对应拘束器[i]; + sa.DrawGuidance(sa.Data.PartyList[i], _upm.拘束器坐标[bhIdx], 0, 10000, $"P3C_{_upm.当前阶段}_进拘束器指路{i}", + sa.Data.DefaultSafeColor); + } + } + if (_upm.P3.灾厄台词计数 == 2) + { + var peopleIdx = _upm.P3.灾厄对应拘束器[2]; + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (i <= 1) continue; + if (!Debugging && sa.GetMyIndex() != i) continue; + sa.DrawGuidance(sa.Data.PartyList[i], _upm.拘束器坐标[peopleIdx], 0, 10000, $"P3C_{_upm.当前阶段}_进拘束器指路{i}", + sa.Data.DefaultSafeColor); + } + } + } + + [ScriptMethod(name: "P3C_以太失控后陨石流", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9905"], + userControl: true, suppress: 500)] + public void P3C_以太失控后陨石流(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 is not (3300 or 3350)) return; + var color = new Vector4(0.4f, 1, 1, 1.5f); + 执行台词连续技绘图(sa, NaelQuoteSkills.陨石流, 0, 4000, color); + } + + [ScriptMethod(name: "P3C_转阶段与刷新技能", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9905"], + userControl: Debugging, suppress: 500)] + public void P3C_转阶段与刷新技能(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3300) return; + sa.Method.RemoveDraw(@".*_3300.*"); + + _upm.当前阶段 = 3350; + _upm.P3.获得阶段技能循环轴(_upm.当前阶段); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + #endregion P3C 灾厄的三重奏 3300-3350 + + #region P3D 天地的三重奏 3400-3450 + + [ScriptMethod(name: "=============《P3D 天地的三重奏》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3D_天地的三重奏_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3D_天地阶段转换", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9957"], + userControl: Debugging)] + public void P3D_天地阶段转换(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 3400; + sa.DebugMsg($"{_upm.当前阶段}"); + } + + [ScriptMethod(name: "P3D_天地指路准备", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9957"], + userControl: true)] + public void P3D_天地指路准备(Event ev, ScriptAccessory sa) + { + sa.DrawGuidance(Center, 0, 4000, $"P3D_{_upm.当前阶段}_天地指路场中", sa.Data.DefaultSafeColor); + sa.TextInfo("场中引导俯冲,然后出发", delayMs: 3500); + } + + [ScriptMethod(name: "P3D_天地旋风指路", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9906"], + userControl: true)] + public async void P3D_天地旋风指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3400) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P3.巴哈记录阶段 == _upm.当前阶段, + () => _upm.P3.双塔记录阶段 == _upm.当前阶段, + () => _upm.P3.奈尔记录阶段 == _upm.当前阶段, + ])) return; + + var err = _upm.P3.求解天地旋风方位(); + if (err != 0) return; + + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (!Debugging && sa.GetMyIndex() != i) continue; + var region = _upm.P3.天地旋风方位[i]; + if (region < 0) continue; + + var tPos = new Vector3(0, 0, 20).RotateAndExtend(Center, 45f.DegToRad() * region); + sa.DrawGuidance(sa.Data.PartyList[i], tPos, 0, 3700, + $"P3D_{_upm.当前阶段}_天地旋风指路{i}", sa.Data.DefaultSafeColor); + } + } + + [ScriptMethod(name: "P3D_天地塔方位收集", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9951"], + userControl: Debugging)] + public void P3D_天地塔方位收集(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3400) return; + lock (_stateLock) + { + var spos = ev.SourcePosition; + var towerRegion = spos.GetRadian(Center).RadianToRegion(16, isDiagDiv: true); + _upm.P3.天地塔方位.Add(towerRegion); + } + } + + [ScriptMethod(name: "P3D_天地塔指路", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9951"], + userControl: true, suppress: 1000)] + public async void P3D_天地塔指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3400) return; + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P3.天地塔方位.Count == 8 + ])) return; + + var err = _upm.P3.求解天地踩塔方位(); + if (err != 0) return; + + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (!Debugging && sa.GetMyIndex() != i) continue; + var region = _upm.P3.天地踩塔方位[i]; + if (region < 0) continue; + + var tPos = new Vector3(0, 0, 10).RotateAndExtend(Center, 22.5f.DegToRad() * region); + sa.DrawGuidance(sa.Data.PartyList[i], tPos, 0, 6500, + $"P3D_{_upm.当前阶段}_天地踩塔指路{i}_击退位置", sa.Data.DefaultSafeColor); + sa.DrawLine(Center, 0, 0, 6500, + $"P3D_{_upm.当前阶段}_天地踩塔指路{i}_指引线", tPos.GetRadian(Center), 20f, 25f, + sa.Data.DefaultSafeColor); + + if (!SpecialMode) continue; + sa.DrawCountDown(sa.Data.PartyList[i], 50); + } + } + + [ScriptMethod(name: "P3D_中心塔击退", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9911"], + userControl: true)] + public void P3D_中心塔击退(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3400) return; + var color = new Vector4(0.4f, 1, 1, 1.5f); + + var dp = sa.DrawRect(Center, 0, 5000, + $"P3D_{_upm.当前阶段}_诸神黄昏即死区", 0, 9, 7, color, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + + sa.DrawKnockBack(Center, 0, 5000, + $"P3D_{_upm.当前阶段}_天崩地裂击退", + 1.5f, 12f, sa.Data.DefaultDangerColor.WithW(2f)); + } + + [ScriptMethod(name: "P3D_转阶段与刷新技能", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0075"], + userControl: Debugging)] + public void P3D_转阶段与刷新技能(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3400) return; + _upm.当前阶段 = 3450; + _upm.P3.获得阶段技能循环轴(_upm.当前阶段); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + #endregion P3D 天地的三重奏 3400-3450 + + #region P3E 连击的三重奏 3500-3550 + + [ScriptMethod(name: "=============《P3E 连击的三重奏》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3E_连击的三重奏_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3E_连击阶段转换", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9958"], + userControl: Debugging)] + public void P3E_连击阶段转换(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 3500; + _pd.Init("P3连击"); + _pd.AddPriorities([1, 2, 3, 4, 5, 6, 7, 8]); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + [ScriptMethod(name: "P3E_连击指路准备", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9958"], + userControl: true)] + public void P3E_连击指路准备(Event ev, ScriptAccessory sa) + { + var basePos = new Vector3(0, 0, 15); + List rotDeg = [-120, 120, -155, 155, -85, 85, -20, 20]; + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var color = i switch + { + 0 or 1 => new Vector4(0.1f, 0.1f, 1, 1), + 2 or 3 => new Vector4(0.1f, 1f, 0.1f, 1), + _ => new Vector4(1, 0.1f, 0.1f, 1), + }; + sa.DrawLine(Center, 0, 0, 6500, $"P3E_{_upm.当前阶段}_连击指路准备_指引线{i}", + rotDeg[i].DegToRad(), 20f, 25f, color); + + if (!Debugging && sa.GetMyIndex() != i) continue; + var member = sa.Data.PartyList[i]; + sa.DrawGuidance(member, basePos.RotateAndExtend(Center, rotDeg[i].DegToRad()), + 0, 6500, $"P3E_{_upm.当前阶段}_连击指路准备{i}", sa.Data.DefaultSafeColor); + } + } + + [ScriptMethod(name: "P3E_连击阶段陨石流范围", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9958"], + userControl: true)] + public void P3E_连击阶段陨石流范围(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + var color = new Vector4(0.4f, 1, 1, 1.5f); + 执行台词连续技绘图(sa, NaelQuoteSkills.陨石流, 4000, 15000, color); + } + + [ScriptMethod(name: "P3E_连击阶段三点一线", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9958"], + userControl: true)] + public void P3E_连击阶段三点一线(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + + var color = new Vector4(1f, 1f, 0f, 1f); + for (var i = 0; i < _upm.拘束器坐标.Count; i++) + { + var rad = _upm.拘束器坐标[i].GetRadian(Center); + sa.DrawLine(Center, 0, 4000, 10000, $"P3E_{_upm.当前阶段}_连击阶段三点一线", rad, 20f, 25f, color); + } + } + + [ScriptMethod(name: "P3E_黑球搭档连线", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0076"], + userControl: true, suppress: 500)] + public async void P3E_黑球搭档连线(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.SelectSpecificPriorityIndex(2, true).Value >= 100, + ])) return; + + if (!Debugging && _pd[sa.GetMyIndex()] < 100) return; + var color = new Vector4(1, 1, 0, 1); + + for (int i = 0; i < 3; i++) + { + var idx1 = _pd.SelectSpecificPriorityIndex(i, true).Key; + var idx2 = _pd.SelectSpecificPriorityIndex((i + 1) % 3, true).Key; + sa.DrawConnection(sa.Data.PartyList[idx1], sa.Data.PartyList[idx2], + 0, 20000, $"P3E_{_upm.当前阶段}_黑球搭档连线{i}", color); + } + } + + [ScriptMethod(name: "P3E_黑球队列计算", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0076"], + userControl: Debugging, suppress: 500)] + public async void P3E_黑球队列计算(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + if (!_upm.P3.连击撞球截球玩家.Contains(-1)) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.SelectSpecificPriorityIndex(2, true).Value >= 100, + ])) return; + + _upm.P3.连击点名玩家 = _pd.SelectLargePriorityIndices(3).Select(x => x.Key).ToList(); + _upm.P3.求解连击撞球截球玩家(); + sa.DebugMsg($"撞球截球玩家:{string.Join(", ", + _upm.P3.连击撞球截球玩家.Select(x => sa.GetPlayerJobByIndex(x)))}"); + } + + [ScriptMethod(name: "P3E_黑球指路", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"], + userControl: true)] + public async void P3E_黑球指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + if (_upm.P3.连击黑球绘图完成) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => !_upm.P3.连击撞球截球玩家.Contains(-1), + ])) return; + var ls = _upm.P3.连击撞球截球玩家.ToArray(); + var myIndex = sa.GetMyIndex(); + if (!sa.IsValidPartyIndex(myIndex)) return; + + for (int i = 0; i < 3; i++) + { + var pidx1 = ls[i]; + var pidx2 = ls[i + 3]; + if (!Debugging && myIndex != pidx1 && myIndex != pidx2) continue; + sa.DrawGuidance(sa.Data.PartyList[pidx1], _upm.拘束器坐标[i], + 0, 5000, $"P3E_{_upm.当前阶段}_黑球指路{pidx1}", sa.Data.DefaultSafeColor); + sa.DrawGuidance(sa.Data.PartyList[pidx2], _upm.拘束器坐标[i], + 0, 5000, $"P3E_{_upm.当前阶段}_黑球指路{pidx2}准备", sa.Data.DefaultDangerColor); + sa.DrawGuidance(sa.Data.PartyList[pidx2], _upm.拘束器坐标[i], + 5000, 10000, $"P3E_{_upm.当前阶段}_黑球指路{pidx2}", sa.Data.DefaultSafeColor); + } + _upm.P3.连击黑球绘图完成 = true; + } + + [ScriptMethod(name: "P3E_大地摇动点名收集", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"], + userControl: Debugging)] + public void P3E_大地摇动点名收集(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + lock (_stateLock) + { + if (_pd.ActionCount >= 4) + return; + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tidx)) return; + _pd.AddPriority(tidx, 1000); + _pd.AddActionCount(); + sa.Method.RemoveDraw($"GEN_拘束器内黑球爆炸范围.*"); + } + } + + [ScriptMethod(name: "P3E_大地摇动搭档连线", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"], + userControl: true, suppress: 500)] + public async void P3E_大地摇动搭档连线(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.ActionCount == 4, + () => _upm.P3.大地摇动搭档连线绘图版本 <= _upm.P3.大地摇动判定次数 + ])) return; + + _upm.P3.大地摇动搭档连线绘图版本++; + var color = new Vector4(1, 1, 0, 1); + + List pidx = _upm.P3.大地摇动搭档连线绘图版本 == 1 + ? _pd.SelectLargePriorityIndices(4).Select(x => x.Key).ToList() + : _pd.SelectSmallPriorityIndices(4).Select(x => x.Key).ToList(); + + if (!Debugging && !pidx.Contains(sa.GetMyIndex())) return; + for (int i = 0; i < 4; i++) + { + var draw = sa.DrawConnection(sa.Data.PartyList[pidx[i]], sa.Data.PartyList[pidx[(i + 1) % 4]], 0, 5000, + $"P3E_{_upm.当前阶段}_大地摇动搭档连线{i}", color, draw: false); + var i1 = i; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, draw, dp => + { + List tempPidx = pidx + .Select(x => new { Index = x, Member = sa.GetById(sa.Data.PartyList[x]) }) + .Where(x => x.Member != null) + .OrderBy(x => x.Member.Position.GetRadian(Center)) + .Select(x => x.Index) + .ToList(); + if (tempPidx.Count != pidx.Count) return; + dp.Owner = sa.Data.PartyList[tempPidx[i1]]; + dp.TargetObject = sa.Data.PartyList[tempPidx[(i1 + 1) % tempPidx.Count]]; + }); + } + } + + [ScriptMethod(name: "P3E_大地摇动指引线", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"], + userControl: true, suppress: 500)] + public async void P3E_大地摇动指引线(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.ActionCount == 4, + () => _upm.P3.大地摇动指引线绘图版本 <= _upm.P3.大地摇动判定次数 + ])) return; + + _upm.P3.大地摇动指引线绘图版本++; + float[] rotDegs = _upm.P3.大地摇动指引线绘图版本 == 1 ? [-40, 40, -100, 100]: [-80, 80, -140, 140]; + var isFirstRound = _pd.FindPriorityIndexOfKey(sa.GetMyIndex(), true) <= 3; + if (!Debugging && (isFirstRound ^ (_upm.P3.大地摇动指引线绘图版本 == 1))) return; + + var color = Vector4.One; + foreach (var deg in rotDegs) + sa.DrawLine(Center, 0, 0, 5000, $"P3E_{_upm.当前阶段}_大地摇动指引线", deg.DegToRad(), 20f, 25, color); + } + + [ScriptMethod(name: "P3E_大地摇动指路", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0028"], + userControl: true, suppress: 500)] + public async void P3E_大地摇动指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.ActionCount == 4, + () => _upm.P3.大地摇动指路绘图版本 <= _upm.P3.大地摇动判定次数 + ])) return; + + _upm.P3.大地摇动指路绘图版本++; + var safePos = _upm.P3.大地摇动指路绘图版本 == 1 ? new Vector3(0, 0, -8.5f) : new Vector3(0, 0, 8.5f); + + List pidx = _upm.P3.大地摇动指路绘图版本 == 1 + ? _pd.SelectLargePriorityIndices(4).Select(x => x.Key).ToList() + : _pd.SelectSmallPriorityIndices(4).Select(x => x.Key).ToList(); + var myIndex = sa.GetMyIndex(); + + // float[] rotDegs = _upm.P3.大地摇动指路绘图版本 == 1 + // ? [-100, -20, 20, 100] + // : [-140, -80, 80, 140]; + + for (int i = 0; i < pidx.Count; i++) + { + var memberIndex = pidx[i]; + if (!Debugging && myIndex != memberIndex) continue; + var member = sa.Data.PartyList[memberIndex]; + + // 不太理想,思路保留,功能注释 + // var drawGuidance = sa.DrawGuidance(member, Center, + // 0, 5000, $"P3E_{_upm.当前阶段}_大地摇动指路{memberIndex}", sa.Data.DefaultSafeColor, draw: false); + // sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, drawGuidance, dp => + // { + // List tempPidx = pidx + // .Select(x => new { Index = x, Member = sa.GetById(sa.Data.PartyList[x]) }) + // .Where(x => x.Member != null) + // .OrderBy(x => x.Member!.Position.GetRadian(Center)) + // .Select(x => x.Index) + // .ToList(); + // if (tempPidx.Count != rotDegs.Length) return; + // var targetIdx = tempPidx.IndexOf(memberIndex); + // if (targetIdx < 0) return; + // dp.TargetPosition = new Vector3(0, 0, 15).RotateAndExtend(Center, rotDegs[targetIdx].DegToRad()); + // }); + + if (SpecialMode) + sa.DrawCountDown(member, 50); + if (myIndex != memberIndex) continue; + sa.TextInfo("引导大地摇动"); + sa.TTS("引导大地摇动"); + } + + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + if (pidx.Contains(i)) continue; + if (!Debugging && myIndex != i) continue; + sa.DrawGuidance(sa.Data.PartyList[i], safePos, + 0, 5000, $"P3E_{_upm.当前阶段}_大地摇动指路{i}", sa.Data.DefaultSafeColor); + if (myIndex != i) continue; + sa.TextInfo("前往安全区,四角预占位"); + sa.TTS("前往安全区,四角预占位"); + } + } + + [ScriptMethod(name: "P3E_转阶段与刷新技能", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9946"], + userControl: Debugging, suppress: 500)] + public void P3E_转阶段与刷新技能(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3500) return; + _upm.P3.大地摇动判定次数++; + if (_upm.P3.大地摇动判定次数 < 2) return; + sa.Method.RemoveDraw(@".*_3500.*"); + _upm.当前阶段 = 3550; + _upm.P3.获得阶段技能循环轴(_upm.当前阶段); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + #endregion P3E 连击的三重奏 3500-3550 + + #region P3F 群龙的八重奏 3600 + + [ScriptMethod(name: "=============《P3F 群龙的八重奏》=============", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3F_群龙的八重奏_分割线(Event ev, ScriptAccessory sa) + { + } + + + [ScriptMethod(name: "P3F_群龙阶段转换", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9959"], + userControl: Debugging)] + public void P3F_群龙阶段转换(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 3600; + _pd.Init($"P3群龙"); + sa.DebugMsg($"{_upm.当前阶段}"); + sa.Method.RemoveDraw(@".*_3550.*"); + } + + [ScriptMethod(name: "P3F_群龙指路准备", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9959"], + userControl: true)] + public void P3F_群龙指路准备(Event ev, ScriptAccessory sa) + { + sa.DrawGuidance(Center, 0, 4000, $"P3F_{_upm.当前阶段}_群龙指路场中", sa.Data.DefaultSafeColor); + } + + [ScriptMethod(name: "P3F_群龙跑圈指路", + eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:8168"], + userControl: true, suppress: 500)] + public async void P3F_群龙跑圈指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _upm.P3.奈尔记录阶段 == 3600, + () => _upm.P3.巴哈记录阶段 == 3600, + () => _upm.P3.双塔记录阶段 == 3600, + ])) return; + + var err = _upm.P3.求解群龙起跑方位与方向(); + if (err != 0) return; + + var rad1 = _upm.P3.群龙起跑方位与方向[0] * 45f.DegToRad(); + var rad2 = rad1 + _upm.P3.群龙起跑方位与方向[1] * 45f.DegToRad(); + + var tPos1 = new Vector3(0, 0, 22).RotateAndExtend(Center, rad1); + var tPos2 = new Vector3(0, 0, 22).RotateAndExtend(Center, rad2); + + sa.DrawGuidance(tPos1, 0, 5000, $"P3F_{_upm.当前阶段}_群龙跑圈指路_起跑点准备", sa.Data.DefaultDangerColor); + sa.DrawGuidance(tPos1, tPos2, 5000, 4000, $"P3F_{_upm.当前阶段}_群龙跑圈指路_起跑点", sa.Data.DefaultDangerColor); + + sa.DrawGuidance(tPos1, 5000, 5500, $"P3F_{_upm.当前阶段}_群龙跑圈指路_起跑点", sa.Data.DefaultSafeColor); + sa.DrawGuidance(tPos1, tPos2, 5000, 5500, $"P3F_{_upm.当前阶段}_群龙跑圈指路_起跑点", sa.Data.DefaultDangerColor); + + sa.DrawGuidance(tPos2, 10500, 2000, $"P3F_{_upm.当前阶段}_群龙跑圈指路_开跑", sa.Data.DefaultSafeColor); + } + + [ScriptMethod(name: "P3F_群龙起跑提示", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0077"], + userControl: true)] + public void P3F_群龙起跑提示(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + var dirStr = _upm.P3.群龙起跑方位与方向[1] == 1 ? "左" : "右"; + sa.TextInfo($"等待奈尔冲锋后,面向场外向【{dirStr}】跑", destroyMs: 4000); + sa.TTS($"即将向{dirStr}跑"); + if (!SpecialMode) return; + sa.DrawCountDown(sa.Data.Me, 500); + } + + [ScriptMethod(name: "P3F_群龙点名记录", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0077|0029|0014)$"], + userControl: Debugging)] + public void P3F_群龙点名记录(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + lock (_stateLock) + { + if (_pd.ActionCount >= 7) return; + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tidx)) return; + _pd.AddPriority(tidx, 10); + _pd.AddActionCount(); + } + } + + [ScriptMethod(name: "P3F_回中提示与引导双塔指路", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0029)$"], + userControl: true)] + public async void P3F_回中提示与引导双塔指路(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + + if (!await WaitUntilConditions( + timeoutMs: 500, + conditions: + [ + () => _pd.ActionCount == 7, + ])) return; + + var tidx = _pd.SelectSpecificPriorityIndex(0).Key; + var myIndex = sa.GetMyIndex(); + + if (myIndex != tidx) + sa.DrawGuidance(Center, 0, 5000, $"P3F_{_upm.当前阶段}_回中提示", sa.Data.DefaultSafeColor); + + if (Debugging || myIndex == tidx) + { + var member = sa.Data.PartyList[tidx]; + if (sa.GetById(member) is not { } obj) return; + var tPos1 = new Vector3(0, 0, 22).RotateAndExtend(Center, (_upm.P3.双塔方位 * 45f + 13f).DegToRad() ); + var tPos2 = new Vector3(0, 0, 22).RotateAndExtend(Center, (_upm.P3.双塔方位 * 45f - 13f).DegToRad() ); + var draw = sa.DrawGuidance(member, 0, 0, 9000, $"P3F_{_upm.当前阶段}_双塔引导指路", sa.Data.DefaultSafeColor, + draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, draw, dp => + { + if (!obj.IsValid()) return; + var distanceTo1 = Vector3.Distance(obj.Position, tPos1); + var distanceTo2 = Vector3.Distance(obj.Position, tPos2); + dp.TargetPosition = distanceTo1 < distanceTo2 ? tPos1 : tPos2; + }); + } + } + + [ScriptMethod(name: "P3F_分摊点名记录", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0027"], + userControl: true)] + public void P3F_分摊点名记录(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + lock (_stateLock) + { + if (_pd.ActionCount >= 11) return; + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (!sa.IsValidPartyIndex(tidx)) return; + _pd.AddPriority(tidx, 100); + _pd.AddActionCount(); + } + } + + [ScriptMethod(name: "P3F_踩塔搭档连线", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0027"], + userControl: true, suppress: 500)] + public async void P3F_踩塔搭档连线(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.ActionCount == 11, + ])) return; + + var color = new Vector4(1, 1, 0, 1); + + List pidx = _pd.SelectSmallPriorityIndices(4).Select(x => x.Key).ToList(); + + if (!Debugging && !pidx.Contains(sa.GetMyIndex())) return; + for (int i = 0; i < 4; i++) + { + var draw = sa.DrawConnection(sa.Data.PartyList[pidx[i]], sa.Data.PartyList[pidx[(i + 1) % 4]], 0, 7000, + $"P3F_{_upm.当前阶段}_踩塔搭档连线{i}", color, draw: false); + var i1 = i; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, draw, dp => + { + List tempPidx = pidx + .Select(x => new { Index = x, Member = sa.GetById(sa.Data.PartyList[x]) }) + .Where(x => x.Member != null) + .OrderBy(x => x.Member.Position.GetRadian(Center)) + .Select(x => x.Index) + .ToList(); + if (tempPidx.Count != pidx.Count) return; + dp.Owner = sa.Data.PartyList[tempPidx[i1]]; + dp.TargetObject = sa.Data.PartyList[tempPidx[(i1 + 1) % tempPidx.Count]]; + }); + } + } + + [ScriptMethod(name: "P3F_踩塔与躲避提示", + eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0027"], + userControl: true, suppress: 500)] + public async void P3F_踩塔与躲避提示(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.ActionCount == 11, + ])) return; + + var myIndex = sa.GetMyIndex(); + var priVal = _pd[myIndex]; + if (priVal >= 100 && myIndex <= 1) + { + sa.TextInfo("带着分摊踩塔"); + sa.TTS("带着分摊踩塔"); + } + else if (priVal >= 100) + { + sa.TextInfo("避开塔"); + sa.TTS("避开塔"); + } + else + { + sa.TextInfo("踩塔"); + sa.TTS("踩塔"); + } + } + + // [ScriptMethod(name: "P3F_踩塔判定倒计时", + // eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9951"], + // userControl: true)] + // public void P3F_踩塔判定倒计时(Event ev, ScriptAccessory sa) + // { + // if (_upm.当前阶段 != 3600) return; + // if (!SpecialMode) return; + // lock (_stateLock) + // { + // _upm.P3.塔头标偏移++; + // sa.DrawCountDown(ev.SourcePosition, 3000, iconScale: 1f, objIdBias: _upm.P3.塔头标偏移); + // } + // + // } + + [ScriptMethod(name: "P3F_删除绘图与转阶段", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(9906)$", "TargetIndex:1"], + userControl: Debugging)] + public void P3F_删除绘图与转阶段(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 3600) return; + _upm.当前阶段 = 4000; + sa.Method.RemoveDraw($".*"); + _pd.Init($"P4黑球"); + sa.DebugMsg($"{_upm.当前阶段}"); + } + + #endregion P3F 群龙的八重奏 3600 + + #endregion P3 + + #region P4 + + [ScriptMethod(name: "———————— 《P4》 ————————", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P4_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P4_拉怪位置显示", + eventType: EventTypeEnum.Targetable, eventCondition: ["DataId:8161", "Targetable:True"], + userControl: Debugging)] + public void P4_拉怪位置显示(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + for (int i = 0; i < 2; i++) + { + if (!Debugging && sa.GetMyIndex() != i) continue; + sa.DrawGuidance(sa.Data.PartyList[i], _upm.拉怪位置, 0, 5000, + $"P4_{_upm.当前阶段}_拉怪位置", sa.Data.DefaultSafeColor); + } + + var color = new Vector4(1f, 0.5f, 0.5f, 0.75f); + sa.DrawCircle(_upm.拉怪位置, 0, 140000, $"P4_{_upm.当前阶段}_拉怪位置", 1f, color); + } + + [ScriptMethod(name: "P4_双Boss中心显示", + eventType: EventTypeEnum.Targetable, eventCondition: ["DataId:8161", "Targetable:True"], + userControl: Debugging)] + public void P4_双Boss中心显示(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + sa.DrawCircle(_upm.P1.双塔尼亚_ObjId, 0, Int32.MaxValue, + $"P4_{_upm.当前阶段}_双塔尼亚中心点_内圆", 0.4f, new Vector4(1, 0, 0, 2), useImgui: true); + sa.DrawDonut(_upm.P1.双塔尼亚_ObjId, 0, Int32.MaxValue, + $"P4_{_upm.当前阶段}_双塔尼亚中心点_外环", 0.5f, 0.4f, new Vector4(0, 1, 1, 1), useImgui: true); + sa.DrawCircle(_upm.P2.奈尔_ObjId, 0, Int32.MaxValue, + $"P4_{_upm.当前阶段}_奈尔中心点_内圆", 0.4f, new Vector4(1, 0, 0, 2), useImgui: true); + sa.DrawDonut(_upm.P2.奈尔_ObjId, 0, Int32.MaxValue, + $"P4_{_upm.当前阶段}_奈尔中心点_外环", 0.5f, 0.4f, new Vector4(0, 1, 1, 1), useImgui: true); + } + + [ScriptMethod(name: "P4_第一次垂直下落", + eventType: EventTypeEnum.Targetable, eventCondition: ["DataId:8161", "Targetable:True"], + userControl: Debugging)] + public void P4_第一次垂直下落(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + 垂直下落绘图(sa); + } + + [ScriptMethod(name: "P4_第二次垂直下落", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9897", "TargetIndex:1"], + userControl: Debugging)] + public void P4_第二次垂直下落(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + 垂直下落绘图(sa); + } + + [ScriptMethod(name: "P4_液体地狱引导范围", + eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(9896)$", "TargetIndex:1"], + userControl: true)] + public void P4_液体地狱引导范围(Event ev, ScriptAccessory sa) + { + // 液体地狱只会在垂直下落后 + if (_upm.当前阶段 != 4000) return; + 液体地狱引导范围绘图(sa, phaseKeep: true); + } + + [ScriptMethod(name: "P4_台词连续技", + eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(650[4567])$"], + userControl: Debugging)] + public void P4_台词连续技(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + var quoteId = ev.Id0(); + var color = new Vector4(0.4f, 1, 1, 1.5f); + switch (quoteId) + { + case 0x6504: + // 钢铁燃烧吧!成为我降临于此的刀剑吧! + 执行台词连续技绘图(sa, NaelQuoteSkills.钢铁, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.分摊, 5000, 3000, sa.Data.DefaultSafeColor); + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 8000, 3000, color); + 执行分散方向绘图(sa, 8000, 8000); + sa.TextInfo("钢铁 -> 分摊 -> 分散", destroyMs: 5000, isWarning: true); + sa.TTS("钢铁、分摊,然后分散"); + break; + case 0x6505: + // 钢铁成为我降临于此的燃烧之剑! + 执行台词连续技绘图(sa, NaelQuoteSkills.钢铁, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 5000, 3000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.分摊, 8000, 3000, sa.Data.DefaultSafeColor); + 执行分散方向绘图(sa, 11000, 5000); + sa.TextInfo("钢铁 -> 分散 -> 分摊", destroyMs: 5000, isWarning: true); + sa.TTS("钢铁、分散,然后分摊"); + break; + case 0x6506: + // 我自月而来降临于此,踏过炽热之地! + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 5000, 3000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.分摊, 8000, 3000, sa.Data.DefaultSafeColor); + 执行分散方向绘图(sa, 11000, 5000); + sa.TextInfo("月环 -> 分散 -> 分摊", destroyMs: 5000, isWarning: true); + sa.TTS("月环、分散,然后分摊"); + break; + case 0x6507: + // 我自月而来携钢铁降临于此! + 执行台词连续技绘图(sa, NaelQuoteSkills.月环, 0, 5000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.钢铁, 5000, 3000, color); + 执行台词连续技绘图(sa, NaelQuoteSkills.凶鸟冲, 8000, 3000, color); + 执行分散方向绘图(sa, 8000, 8000); + sa.TextInfo("月环 -> 钢铁 -> 分散", destroyMs: 5000, isWarning: true); + sa.TTS("月环、钢铁,然后分散"); + break; + } + } + + private void 执行分散方向绘图(ScriptAccessory sa, int delayMs, int destroyMs) + { + List rotDegs = [20, -20, 105, -105, 60, -60, 150, -150]; + var baseRad = _upm.拉怪位置.GetRadian(Center); + var myIndex = sa.GetMyIndex(); + + for (int i = 0; i < 8; i++) + { + var width = i == myIndex ? 20f : 10f; + var color = i == myIndex ? sa.Data.DefaultSafeColor : Vector4.One; + sa.DrawLine(Center, 0, delayMs, destroyMs, $"P4_{_upm.当前阶段}_分散方向绘图", + baseRad + rotDegs[i].DegToRad(), width, 25, color); + } + } + + [ScriptMethod(name: "P4_黑球指路计算", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"], + userControl: Debugging)] + public async void P4_黑球指路计算(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => _pd.SelectSpecificPriorityIndex(2, true).Value >= 100, + ])) return; + + var keys = _pd.SelectLargePriorityIndices(3).Select(x => x.Key).ToArray(); + _upm.P4.求解黑球撞球序列(keys); + } + + [ScriptMethod(name: "P4_黑球指路与TTS", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9902"], + userControl: true)] + public async void P4_黑球指路与TTS(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + + if (!await WaitUntilConditions( + conditions: + [ + () => !_upm.P4.黑球撞球序列.Contains(-1) + ])) return; + + for (int i = 0; i < 3; i++) + { + var playerIndex = _upm.P4.黑球撞球序列[i]; + if (!Debugging && sa.GetMyIndex() != playerIndex) continue; + + var tPos = _upm.拘束器坐标[i]; + sa.DrawGuidance(sa.Data.PartyList[playerIndex], tPos, 0, 5500, + $"P4_{_upm.当前阶段}_黑球指路", sa.Data.DefaultSafeColor); + + var ttsStr = i == 2 ? "旋风后撞球" : "撞球后旋风"; + sa.DebugMsg($"{sa.GetPlayerJobByIndex(playerIndex)} 撞 {i + 1}: {ttsStr}", order: i); + if (sa.GetMyIndex() != playerIndex) continue; + sa.TextInfo(ttsStr, isWarning: true); + sa.TTS(ttsStr); + } + } + + [ScriptMethod(name: "P4_黑球计算重置", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9902"], + userControl: Debugging)] + public void P4_黑球计算重置(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 4000) return; + _pd.Init($"P4黑球"); + _upm.P4.黑球撞球序列 = [-1, -1, -1]; + } + + #endregion P4 + + #region P5 + + [ScriptMethod(name: "———————— 《P5》 ————————", + eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5_转阶段", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:9970", "TargetIndex:1"], + userControl: Debugging)] + public void P5_转阶段(Event ev, ScriptAccessory sa) + { + _upm.当前阶段 = 5000; + sa.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "P5_无尽顿悟分摊", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9964"], + userControl: true)] + public void P5_无尽顿悟分摊(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 5000) return; + _upm.P5.分摊轮数++; + sa.TextInfo($"分摊 #{_upm.P5.分摊轮数}", destroyMs: 4000, isWarning: true); + sa.TTS($"分摊第{_upm.P5.分摊轮数}轮"); + var color = sa.Data.DefaultSafeColor.WithW(3); + sa.DrawCircle(ev.TargetId, 0, 6000, $"P5_{_upm.当前阶段}_无尽顿悟分摊", 4f, color, byTime: true); + } + + [ScriptMethod(name: "P5_死亡轮回死刑", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9962"], + userControl: true)] + public void P5_死亡轮回死刑(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 5000) return; + _upm.P5.死刑轮数++; + var destroyMs = 6500 + 1000 * _upm.P5.死刑轮数; + sa.TextInfo($"死刑 #{_upm.P5.死刑轮数}", destroyMs: 4000, isWarning: true); + sa.TTS($"死刑第{_upm.P5.死刑轮数}轮"); + var color = sa.GetMyIndex() <= 1 ? sa.Data.DefaultSafeColor.WithW(3) : sa.Data.DefaultDangerColor.WithW(3); + sa.DrawCircle(ev.TargetId, 0, destroyMs, $"P5_{_upm.当前阶段}_死亡轮回死刑", 4f, color); + } + + [ScriptMethod(name: "P5_百京核爆地火(起爆)", + eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:9968"], + userControl: true)] + public void P5_百京核爆地火起爆(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 5000) return; + var spos = ev.SourcePosition; + var srot = ev.SourceRotation; + var explodeColor = new Vector4(0f, 1f, 1f, 1.5f); + var warnColor = new Vector4(0f, 0.5f, 1f, 1f); + + // 故意让第二枚告警延长一段时间,避免消失 -> 出现过于突兀 + int[] destroyMs = [4000, 4250, 6000]; + + for (int i = 0; i < 3; i++) + { + var pos = spos.RotateAndExtend(spos, srot, i * 8); + var color = i == 0 ? explodeColor : warnColor.WithW(0.8f / i); + sa.DrawCircle(pos, 0, destroyMs[i], $"P5_百京核爆_起爆源", 6f, color, byTime: i == 0); + } + } + + [ScriptMethod(name: "P5_百京核爆地火(后续)", + eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(996[89])$", "TargetIndex:1"], + userControl: true)] + public void P5_百京核爆地火后续(Event ev, ScriptAccessory sa) + { + if (_upm.当前阶段 != 5000) return; + var srot = ev.SourceRotation; + var spos = ev.SourcePosition; + var explodeColor = new Vector4(0f, 1f, 1f, 1.5f); + var warnColor = new Vector4(0f, 0.5f, 1f, 1f); + + // 故意让第二枚告警延长一段时间,避免消失 -> 出现过于突兀 + int[] destroyMs = [1500, 1750, 3000]; + + for (int i = 0; i < 3; i++) + { + var pos = spos.RotateAndExtend(spos, srot, (i + 1) * 8); + var color = i == 0 ? explodeColor : warnColor.WithW(0.8f / i); + sa.DrawCircle(pos, 0, destroyMs[i], $"P5_百京核爆_后续", 6f, color, byTime: i == 0); + } + } + + #endregion P5 + +} + +#region 优先级字典 +internal class PriorityEntry +{ + public int Key { get; set; } + public string Name { get; set; } = ""; + public int Value { get; set; } +} + +internal class PriorityDict +{ + public Dictionary Entries { get; set; } = []; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + private static readonly List DefaultName = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + + /// + /// 索引器,直接读写对应Key的Value + /// 若Key不存在则抛出异常 + /// + public int this[int key] + { + get => Entries[key].Value; + set => Entries[key].Value = value; + } + + public void Init(string annotation, int entryCount = 8, + List? names = null, bool refreshActionCount = true) + { + Entries.Clear(); + + // names 长度校验 + if (names != null && names.Count != entryCount) + throw new ArgumentException($"names 长度({names.Count})与 entryCount ({entryCount})不一致"); + + // 确定每个条目的Name + List resolvedNames; + if (names != null) + { + resolvedNames = names; + } + else if (entryCount == DefaultName.Count) + { + resolvedNames = DefaultName; + } + else + { + throw new ArgumentException($"entryCount = {entryCount} 无默认 Name,请提供 names 参数"); + } + + for (var i = 0; i < entryCount; i++) + { + Entries.Add(i, new PriorityEntry { Key = i, Name = resolvedNames[i], Value = 0 }); + } + Annotation = annotation; + if (refreshActionCount) + ActionCount = 0; + } + + /// + /// 为特定Key增加优先级 + /// + /// key + /// 优先级数值 + public void AddPriority(int key, int priority) + { + if (!Entries.TryGetValue(key, out var entry)) + throw new KeyNotFoundException($"Key {key} 不存在"); + entry.Value += priority; + } + + /// + /// 从Entries中找到前num个数值最小的,得到新的列表返回 + /// + /// + /// + public List SelectSmallPriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num); + } + + /// + /// 从Entries中找到前num个数值最大的,得到新的列表返回 + /// + /// + /// + public List SelectLargePriorityIndices(int num) + { + return SelectMiddlePriorityIndices(0, num, true); + } + + /// + /// 从Entries中找到升序排列中间的数值,得到新的列表返回 + /// + /// 跳过skip个元素。若从第二个开始取,skip=1 + /// 取num个元素 + /// 降序排列,默认为false + /// + public List SelectMiddlePriorityIndices(int skip, int num, bool descending = false) + { + if (Entries.Count < skip + num) + return new List(); + + IOrderedEnumerable sortedEntries = descending + ? Entries.Values.OrderByDescending(e => e.Value).ThenBy(e => e.Key) + : Entries.Values.OrderBy(e => e.Value).ThenBy(e => e.Key); + + return sortedEntries.Skip(skip).Take(num).ToList(); + } + + /// + /// 从Entries中找到升序排列第idx位的数据,返回 + /// + /// + /// 降序排列,默认为false + /// + public PriorityEntry SelectSpecificPriorityIndex(int idx, bool descending = false) + { + if (idx < 0 || idx >= Entries.Count) + throw new ArgumentOutOfRangeException(nameof(idx)); + + IOrderedEnumerable sortedEntries = descending + ? Entries.Values.OrderByDescending(e => e.Value).ThenBy(e => e.Key) + : Entries.Values.OrderBy(e => e.Value).ThenBy(e => e.Key); + + return sortedEntries.ElementAt(idx); + } + + /// + /// 从Entries中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + if (!Entries.TryGetValue(key, out var targetEntry)) + throw new ArgumentOutOfRangeException(nameof(key)); + + int count = 0; + foreach (var pair in Entries) + { + if (pair.Key == key) continue; + + bool isBetter = descending + ? pair.Value.Value > targetEntry.Value + : pair.Value.Value < targetEntry.Value; + + if (isBetter) count++; + else if (pair.Value.Value == targetEntry.Value && pair.Key < key) + count++; + } + return count; + } + + /// + /// 一次性增加优先级数值 + /// 通常适用于特殊优先级(如H-T-D-H) + /// + /// + public void AddPriorities(List priorities) + { + if (Entries.Count != priorities.Count) + throw new ArgumentException("输入的列表与内部设置长度不同"); + + for (var i = 0; i < Entries.Count; i++) + AddPriority(i, priorities[i]); + } + + /// + /// 输出优先级字典的Key与优先级 + /// + /// + public string ShowPriorities(bool showName = true) + { + var str = $"{Annotation} ({ActionCount}-th) 优先级字典:\n"; + if (Entries.Count == 0) + { + str += $"PriorityDict Empty.\n"; + return str; + } + foreach (var entry in Entries.Values) + { + str += $"Key {entry.Key} {(showName ? $"({entry.Name})" : "")}, Value {entry.Value}\n"; + } + + return str; + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + public string ShowGroup(string name, List entryList) + { + return $"{name}:{string.Join(" ", entryList.Select(x => $"({x.Name}, {x.Value})"))}"; + } +} + +#endregion 优先级字典 类 + +#region 参数容器类 +internal class UcobParams +{ + public int 当前阶段 = 0; + public List 拘束器坐标 = []; + public Vector3 拉怪位置 = Vector3.Zero; + public int 液体地狱判定次数 = 0; + + public UcobParamsP1 P1 = new(); + public UcobParamsP2 P2 = new(); + public UcobParamsP3 P3 = new(); + public UcobParamsP4 P4 = new(); + public UcobParamsP5 P5 = new(); + + public void Reset() + { + 当前阶段 = 0; + 拘束器坐标.Clear(); + 拉怪位置 = Vector3.Zero; + 液体地狱判定次数 = 0; + P1.Reset(); + P2.Reset(); + P3.Reset(); + P4.Reset(); + P5.Reset(); + } +} + +internal static class UcobExtension +{ + public static int 获得最近拘束器序列(this UcobParams upm, Vector3 spos) + { + int minIdx = 0; + float minLength = 999f; + for (int i = 0; i < upm.拘束器坐标.Count; i++) + { + float length = upm.拘束器坐标[i].GetLength(spos); + if (length < minLength) + { + minLength = length; + minIdx = i; + } + } + return minIdx; + } + + public static int 求解拉怪位置(this UcobParams upm) + { + if (upm.拘束器坐标.Count != 3) return -1; + var rad0 = upm.拘束器坐标[0].GetRadian(UcobReborn.Center); + var rad1 = upm.拘束器坐标[1].GetRadian(UcobReborn.Center); + var rad = MathF.Atan2(MathF.Sin(rad0) + MathF.Sin(rad1), MathF.Cos(rad0) + MathF.Cos(rad1)); + upm.拉怪位置 = new Vector3(0, 0, 15f).RotateAndExtend(UcobReborn.Center, rad); + return 0; + } +} + +#region P1 Params + +internal class UcobParamsP1 +{ + public int 技能序号 = 0; + public List 技能循环轴 = [TwinTaniaSkills.垂直下落, TwinTaniaSkills.旋风, TwinTaniaSkills.死刑]; + public ulong 双塔尼亚_ObjId = 0; + public int 液体地狱循环轴判断次数 = 0; + + internal const uint 垂直下落 = 9896; + internal const uint 旋风 = 9898; + internal const uint 死刑 = 9897; + internal const uint 液体地狱 = 9901; + internal const uint 黑球 = 9902; + + public void Reset() + { + 技能序号 = 0; + 液体地狱循环轴判断次数 = 0; + this.获得阶段技能循环轴(0); + 双塔尼亚_ObjId = 0; + } +} + +internal enum TwinTaniaSkills +{ + 垂直下落, + 旋风, + 死刑, + 液体地狱, + 液体地狱随机, + 黑球, +} + +internal static class UcobP1Extension +{ + public static void 获得阶段技能循环轴(this UcobParamsP1 p1, int currentPhase) + { + // 根据输入的阶段返回技能循环轴 + p1.技能循环轴 = currentPhase switch + { + 1100 => + [ + TwinTaniaSkills.液体地狱, TwinTaniaSkills.黑球, TwinTaniaSkills.液体地狱, TwinTaniaSkills.死刑, + TwinTaniaSkills.黑球, TwinTaniaSkills.旋风, TwinTaniaSkills.垂直下落 + ], + 1200 => + [ + TwinTaniaSkills.液体地狱, TwinTaniaSkills.黑球, TwinTaniaSkills.液体地狱随机, TwinTaniaSkills.死刑, + TwinTaniaSkills.垂直下落, TwinTaniaSkills.黑球, TwinTaniaSkills.旋风, TwinTaniaSkills.垂直下落 + ], + _ => + [ + TwinTaniaSkills.垂直下落, TwinTaniaSkills.旋风, TwinTaniaSkills.死刑 + ], + }; + } + public static void 增加循环技能序号(this UcobParamsP1 p1) + { + if (p1.技能循环轴.Count == 0) return; + p1.技能序号 = (p1.技能序号 + 1) % p1.技能循环轴.Count; + } +} + +#endregion P1 Params + +#region P2 Params +internal class OuterDragon +{ + public ulong ObjectId { get; set; } + public int Region { get; set; } +} + +internal enum NaelQuoteSkills +{ + 钢铁, + 月环, + 分摊, + 月华冲, + 凶鸟冲, + 陨石流, +} +internal class UcobParamsP2 +{ + public ulong 奈尔_ObjId = 0; + public List 小龙列表 = []; + public bool 死宣一记录完毕 = false; + public int 救世之翼序号 = 0; + public int 贡品序号 = 0; + public int 烈火球轮数 = 0; + public int 烈火球受击玩家记录轮数 = 0; + public List 烈火球受击玩家 = []; + public int 小龙点名轮数 = 0; + public int 小龙范围已处理轮数 = 0; + public int 小龙指路已处理轮数 = 0; + public int 小龙返回已处理轮数 = 0; + public List 小龙俯冲引导点 = []; + public List 小龙俯冲引导玩家 = []; + public void Reset() + { + 奈尔_ObjId = 0; + 小龙列表.Clear(); + 烈火球轮数 = 0; + 烈火球受击玩家记录轮数 = 0; + 烈火球受击玩家 = []; + 死宣参数重置(); + 小龙俯冲参数重置(); + } + + public void 死宣参数重置() + { + 死宣一记录完毕 = false; + 救世之翼序号 = 0; + 贡品序号 = 0; + } + + public void 小龙俯冲参数重置() + { + 小龙点名轮数 = 0; + 小龙范围已处理轮数 = 0; + 小龙指路已处理轮数 = 0; + 小龙返回已处理轮数 = 0; + 小龙俯冲引导点.Clear(); + 小龙俯冲引导玩家.Clear(); + } +} + +internal static class UcobP2Extension +{ + public static void 获得小龙俯冲引导点(this UcobParamsP2 p2) + { + if (p2.小龙列表.Count == 0) return; + List dragonRegionList = p2.小龙列表.Select(t => t.Region).ToList(); + p2.小龙俯冲引导点 = dragonRegionList switch + { + [0, 1, 2, 3, 4] => [11, 5, 7], + [0, 1, 2, 3, 5] => [11, 5, 7], + [0, 1, 2, 3, 6] => [11, 5, 7], + [0, 1, 2, 3, 7] => [11, 5, 8], + [0, 1, 2, 4, 5] => [2, 5, 8], + [0, 1, 2, 4, 6] => [11, 5, 8], + [0, 1, 2, 4, 7] => [2, 5, 8], + [0, 1, 2, 5, 6] => [2, 5, 10], + [0, 1, 2, 5, 7] => [11, 5, 8], + [0, 1, 2, 6, 7] => [2, 5, 8], + [0, 1, 3, 4, 5] => [2, 6, 9], + [0, 1, 3, 4, 6] => [11, 3, 8], + [0, 1, 3, 4, 7] => [2, 6, 8], + [0, 1, 3, 5, 6] => [2, 6, 10], + [0, 1, 3, 5, 7] => [2, 6, 9], + [0, 1, 3, 6, 7] => [11, 3, 8], + [0, 1, 4, 5, 6] => [2, 5, 10], + [0, 1, 4, 5, 7] => [2, 5, 10], + [0, 1, 4, 6, 7] => [11, 5, 8], + [0, 1, 5, 6, 7] => [2, 6, 11], + [0, 2, 3, 4, 5] => [2, 6, 8], + [0, 2, 3, 4, 6] => [2, 6, 8], + [0, 2, 3, 4, 7] => [2, 6, 8], + [0, 2, 3, 5, 6] => [2, 6, 10], + [0, 2, 3, 5, 7] => [2, 6, 9], + [0, 2, 3, 6, 7] => [2, 6, 8], + [0, 2, 4, 5, 6] => [2, 5, 10], + [0, 2, 4, 5, 7] => [2, 5, 9], + [0, 2, 4, 6, 7] => [2, 5, 8], + [0, 2, 5, 6, 7] => [2, 6, 11], + [0, 3, 4, 5, 6] => [2, 8, 10], + [0, 3, 4, 5, 7] => [2, 8, 10], + [0, 3, 4, 6, 7] => [2, 8, 11], + [0, 3, 5, 6, 7] => [2, 6, 11], + [0, 4, 5, 6, 7] => [3, 9, 11], + [1, 2, 3, 4, 5] => [1, 6, 8], + [1, 2, 3, 4, 6] => [1, 6, 8], + [1, 2, 3, 4, 7] => [1, 6, 9], + [1, 2, 3, 5, 6] => [4, 7, 10], + [1, 2, 3, 5, 7] => [1, 6, 9], + [1, 2, 3, 6, 7] => [4, 7, 11], + [1, 2, 4, 5, 6] => [4, 8, 10], + [1, 2, 4, 5, 7] => [1, 5, 9], + [1, 2, 4, 6, 7] => [4, 8, 11], + [1, 2, 5, 6, 7] => [11, 6, 1], + [1, 3, 4, 5, 6] => [3, 8, 10], + [1, 3, 4, 5, 7] => [3, 8, 10], + [1, 3, 4, 6, 7] => [3, 8, 11], + [1, 3, 5, 6, 7] => [3, 9, 11], + [1, 4, 5, 6, 7] => [4, 9, 11], + [2, 3, 4, 5, 6] => [2, 5, 10], + [2, 3, 4, 5, 7] => [2, 7, 10], + [2, 3, 4, 6, 7] => [2, 8, 11], + [2, 3, 5, 6, 7] => [5, 9, 11], + [2, 4, 5, 6, 7] => [5, 9, 11], + [3, 4, 5, 6, 7] => [4, 9, 11], + _ => [] + }; + } +} + +#endregion P2 Params + +#region P3 Params + +internal enum BahamutSkills +{ + 吐息, + 三连吐息, + 夷为平地, + 十亿核爆, + 无 +} + +internal class UcobParamsP3 +{ + public ulong 巴哈_ObjId = 0; + public ulong 奈尔_ObjId = 0; + public ulong 双塔_ObjId = 0; + public int 技能序号 = 0; + public int 吐息已绘图技能序号 = -1; + public List 技能循环轴 = [BahamutSkills.吐息, BahamutSkills.夷为平地, BahamutSkills.无]; + public int 三连吐息判定次数 = 0; + + public Vector3 奈尔_Pos = Vector3.Zero; + public Vector3 双塔_Pos = Vector3.Zero; + public Vector3 巴哈_Pos = Vector3.Zero; + + internal const uint 风暴之翼 = 9943; + internal const uint 吐息 = 9940; + internal const uint 十亿核爆 = 9942; + internal const uint 夷为平地 = 9941; + + public int 奈尔方位 = -1; + public int 双塔方位 = -1; + public int 巴哈方位 = -1; + public int 奈尔记录阶段 = 0; + public int 双塔记录阶段 = 0; + public int 巴哈记录阶段 = 0; + + public int 灾厄台词计数 = 0; + public int[] 灾厄对应拘束器 = [-1, -1, -1]; + + public int[] 天地旋风方位 = [-1, -1, -1, -1, -1, -1, -1, -1]; + public List 天地塔方位 = []; + public int[] 天地踩塔方位 = [-1, -1, -1, -1, -1, -1, -1, -1]; + + public List 连击点名玩家 = []; + public int[] 连击撞球截球玩家 = [-1, -1, -1, -1, -1, -1]; + public bool 连击黑球绘图完成 = false; + public int 大地摇动搭档连线绘图版本 = 0; + public int 大地摇动指引线绘图版本 = 0; + public int 大地摇动指路绘图版本 = 0; + public int 大地摇动判定次数 = 0; + + public int[] 群龙起跑方位与方向 = [-1, -1]; + public uint 塔头标偏移 = 0; + + public void Reset() + { + 巴哈_ObjId = 0; + 奈尔_ObjId = 0; + 双塔_ObjId = 0; + 技能序号 = 0; + 吐息已绘图技能序号 = -1; + 三连吐息判定次数 = 0; + this.获得阶段技能循环轴(0); + + 奈尔记录阶段 = 0; + 双塔记录阶段 = 0; + 巴哈记录阶段 = 0; + 奈尔_Pos = Vector3.Zero; + 双塔_Pos = Vector3.Zero; + 巴哈_Pos = Vector3.Zero; + this.重置Boss方位(); + + 灾厄台词计数 = 0; + 灾厄对应拘束器 = [-1, -1, -1]; + + 天地参数重置(); + 连击参数重置(); + + 群龙起跑方位与方向 = [-1, -1]; + 塔头标偏移 = 0; + } + + public void 天地参数重置() + { + 天地旋风方位 = [-1, -1, -1, -1, -1, -1, -1, -1]; + 天地塔方位 = []; + 天地踩塔方位 = [-1, -1, -1, -1, -1, -1, -1, -1]; + } + + public void 连击参数重置() + { + 连击点名玩家 = []; + 连击撞球截球玩家 = [-1, -1, -1, -1, -1, -1]; + 连击黑球绘图完成 = false; + 大地摇动搭档连线绘图版本 = 0; + 大地摇动指引线绘图版本 = 0; + 大地摇动指路绘图版本 = 0; + 大地摇动判定次数 = 0; + } +} + +internal static class UcobP3Extension +{ + public static void 获得阶段技能循环轴(this UcobParamsP3 p3, int currentPhase) + { + // 根据输入的阶段返回技能循环轴 + p3.技能循环轴 = currentPhase switch + { + 3000 => + [ + BahamutSkills.吐息, BahamutSkills.夷为平地, BahamutSkills.无 + ], + 3150 => + [ + BahamutSkills.吐息, BahamutSkills.夷为平地, BahamutSkills.无 + ], + 3250 => + [ + BahamutSkills.十亿核爆, BahamutSkills.三连吐息, BahamutSkills.无 + ], + 3350 => + [ + BahamutSkills.十亿核爆, BahamutSkills.吐息, BahamutSkills.夷为平地, BahamutSkills.吐息, BahamutSkills.无 + ], + 3450 => + [ + BahamutSkills.十亿核爆, BahamutSkills.三连吐息, BahamutSkills.无 + ], + 3550 => + [ + BahamutSkills.十亿核爆, BahamutSkills.夷为平地, BahamutSkills.吐息, BahamutSkills.无 + ], + _ => + [ + BahamutSkills.吐息, BahamutSkills.夷为平地, BahamutSkills.无 + ], + }; + p3.技能序号 = 0; + p3.吐息已绘图技能序号 = -1; + } + public static void 增加技能序号(this UcobParamsP3 p3) + { + if (p3.技能循环轴.Count == 0) return; + p3.技能序号 += 1; + } + + public static int 求解天地旋风方位(this UcobParamsP3 p3) + { + // MT, H1 固定去基准方位逆时针 90 度(即,基准方位+2) + // D2, D4 固定去基准方位顺时针 90 度(即,基准方位-2) + // ST, H2 固定去奈尔方位 + // D1, D3 固定去奈尔方位对面(即,奈尔方位+4) + + p3.天地旋风方位 = [-1, -1, -1, -1, -1, -1, -1, -1]; + int[] bossDirections = [p3.巴哈方位, p3.奈尔方位, p3.双塔方位]; + + if (bossDirections.Any(direction => direction is < 0 or > 7) || + bossDirections.Distinct().Count() != bossDirections.Length) + return -1; + + var bossDirectionSet = new HashSet(bossDirections); + var baseDirection = -1; + for (var idx = 0; idx < bossDirections.Length; idx++) + { + var candidate = bossDirections[idx]; + if (!bossDirectionSet.Contains((candidate + 7) % 8) || + !bossDirectionSet.Contains((candidate + 1) % 8)) + continue; + + baseDirection = candidate; + break; + } + if (baseDirection == -1) return -1; + + var 基准逆 = (baseDirection + 2) % 8; + var 基准顺 = (baseDirection + 6) % 8; + var 奈尔对面 = (p3.奈尔方位 + 4) % 8; + p3.天地旋风方位 = + [ + 基准逆, p3.奈尔方位, 基准逆, p3.奈尔方位, + 奈尔对面, 基准顺, 奈尔对面, 基准顺 + ]; + return 0; + } + + public static int 求解天地踩塔方位(this UcobParamsP3 p3) + { + // 以奈尔方位为基准方位, + // 从基准方位开始逆时针遇到的塔,踩塔的分别是:ST MT H1 D1 D3 D4 D2 H2 + p3.天地踩塔方位 = [-1, -1, -1, -1, -1, -1, -1, -1]; + + if (p3.奈尔方位 is < 0 or > 7 || + p3.天地塔方位.Count != 8 || + p3.天地塔方位.Any(direction => direction is < 0 or > 15)) + return -1; + + var towerDirectionSet = new HashSet(p3.天地塔方位); + if (towerDirectionSet.Count != p3.天地塔方位.Count) return -1; + + var baseDirection = p3.奈尔方位 * 2 + 1; + int[] playerOrder = [0, 2, 4, 6, 7, 5, 3, 1]; + var towerIdx = 0; + for (var offset = 0; offset < 16 && towerIdx < playerOrder.Length; offset++) + { + var towerDirection = (baseDirection + offset) % 16; + if (!towerDirectionSet.Contains(towerDirection)) continue; + + p3.天地踩塔方位[playerOrder[towerIdx]] = towerDirection; + towerIdx++; + } + return 0; + } + + public static void 求解连击撞球截球玩家(this UcobParamsP3 p3) + { + // 三组撞球与截球的优先级 + // H2 ST D2 D4 D3 D1 MT H1 + int[] pdGroup1 = [3, 1, 5, 7, 6, 4, 0, 2]; + // D3 D4 D1 D2 MT ST H1 H2 + int[] pdGroup2 = [6, 7, 4, 5, 0, 1, 2, 3]; + // H1 MT D1 D4 D3 D2 ST H2 + int[] pdGroup3 = [2, 0, 4, 7, 6, 5, 1, 3]; + + p3.连击撞球截球玩家 = [-1, -1, -1, -1, -1, -1]; + int[][] pdGroups = [pdGroup1, pdGroup2, pdGroup3]; + var 未分配撞球玩家 = new HashSet(p3.连击点名玩家); + var 撞球玩家 = new HashSet(); + var 截球玩家 = new HashSet(); + + for (var pIdx = 0; + pIdx < pdGroups.Min(group => group.Length) && 撞球玩家.Count < 3; + pIdx++) + { + for (var gIdx = 0; gIdx < pdGroups.Length; gIdx++) + { + if (p3.连击撞球截球玩家[gIdx] != -1) continue; + + var player = pdGroups[gIdx][pIdx]; + if (!未分配撞球玩家.Remove(player)) continue; + + p3.连击撞球截球玩家[gIdx] = player; + 撞球玩家.Add(player); + } + } + + for (var gIdx = 0; gIdx < pdGroups.Length; gIdx++) + { + for (var pIdx = 0; pIdx < pdGroups[gIdx].Length; pIdx++) + { + var player = pdGroups[gIdx][pIdx]; + if (撞球玩家.Contains(player) || !截球玩家.Add(player)) continue; + + p3.连击撞球截球玩家[gIdx + 3] = player; + break; + } + } + } + + public static int 求解群龙起跑方位与方向(this UcobParamsP3 p3) + { + // 根据巴哈位置、奈尔位置寻找该机制的起跑方位与跑动方向。 + // 最终赋值给 群龙起跑方位与方向。其中,index 0 代表起跑方位,index 1 的值代表方向 + // 若 index 1 为 -1,则顺时针跑动;为 1,则逆时针跑动 + + // 规则如下: + // 1. 巴哈方位若为正点(0,2,4,6,即正东南西北),逆时针跑;若为斜点,顺时针跑 + // 2. 若巴哈方位正对面(+4)不是奈尔,则起跑点在巴哈方位正对面 + // 3. 若巴哈方位正对面是奈尔,则起跑点基于奈尔方位,往跑动方向顺延 1。 + p3.群龙起跑方位与方向 = [-1, -1]; + + if (p3.巴哈方位 is < 0 or > 7 || p3.奈尔方位 is < 0 or > 7) + return -1; + + var runDirection = p3.巴哈方位 % 2 == 0 ? 1 : -1; + var oppositeBahamut = (p3.巴哈方位 + 4) % 8; + var startDirection = oppositeBahamut != p3.奈尔方位 + ? oppositeBahamut + : (p3.奈尔方位 + runDirection + 8) % 8; + + p3.群龙起跑方位与方向 = [startDirection, runDirection]; + return 0; + } + + public static void 重置Boss方位(this UcobParamsP3 p3) + { + p3.奈尔方位 = -1; + p3.双塔方位 = -1; + p3.巴哈方位 = -1; + } +} + +#endregion P3 Params + +#region P4 Params + +internal class UcobParamsP4 +{ + public int[] 黑球撞球序列 = [-1, -1, -1]; + + public void Reset() + { + 黑球撞球序列 = [-1, -1, -1]; + } +} + +internal static class UcobP4Extension +{ + public static int 求解黑球撞球序列(this UcobParamsP4 p4, int[] targetPlayerKeys) + { + p4.黑球撞球序列 = [-1, -1, -1]; + if (targetPlayerKeys is not { Length: 3 } || + targetPlayerKeys.Any(player => player is < 4 or > 7) || + targetPlayerKeys.Distinct().Count() != targetPlayerKeys.Length) + return -1; + + foreach (var player in targetPlayerKeys.Where(player => player != 7)) + p4.黑球撞球序列[player - 4] = player; + + if (targetPlayerKeys.Contains(7)) + { + var unassignedBall = -1; + for (var i = 0; i < p4.黑球撞球序列.Length; i++) + { + if (p4.黑球撞球序列[i] != -1) continue; + unassignedBall = i; + break; + } + if (unassignedBall == -1) return -1; + p4.黑球撞球序列[unassignedBall] = 7; + } + + return 0; + } +} + +#endregion P4 Params + +#region P5 Params + +internal class UcobParamsP5 +{ + public int 分摊轮数 = 0; + public int 死刑轮数 = 0; + + public void Reset() + { + 分摊轮数 = 0; + 死刑轮数 = 0; + } +} + +internal static class UcobP5Extension +{ +} + +#endregion P5 Params + +#endregion 参数容器类 + +#region 函数集 +internal 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 ev) + { + return ParseHexId(ev["Id"], out var id) ? id : 0; + } + + public static uint DurationMilliseconds(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DurationMilliseconds"]); + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } + + public static uint SourceDataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceDataId"]); + } +} + +internal static class IbcHelper +{ + public static IGameObject? GetById(this ScriptAccessory sa, ulong gameObjectId) + { + return sa.Data.Objects.SearchById(gameObjectId); + } + public static List GetTetherSource(this ScriptAccessory sa, IBattleChara? battleChara, uint tetherId) + { + List tetherSourceId = []; + if (battleChara == null || !battleChara.IsValid()) return []; + unsafe + { + BattleChara* chara = (BattleChara*)battleChara.Address; + var tetherList = chara->Vfx.Tethers; + + foreach (var tether in tetherList) + { + if (tether.Id != tetherId) continue; + tetherSourceId.Add(tether.TargetId.ObjectId); + } + } + return tetherSourceId; + } +} +#region 计算函数 + +internal 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 string ToStr(this Vector3 point, int digits = 2) + => $"({point.X.ToString($"F{digits}")}, {point.Y.ToString($"F{digits}")}, {point.Z.ToString($"F{digits}")})"; + + /// + /// 将任意点以中心点为圆心,逆时针旋转并延长。 + /// + /// 任意点 + /// 中心点 + /// 旋转弧度 + /// 基于该点延伸长度 + /// + public static Vector3 RotateAndExtend(this Vector3 point, Vector3 center, float radian, float length = 0) + { + 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; + } + + /// + /// 获取给定整数的指定位数 + /// + /// 给定数值 + /// 对应位数,个位为0 + /// 返回指定位的数字,如果x超出范围返回0 + public static int GetDecimalDigit(this int val, int x) + => (int)(Math.Abs(val) / Math.Pow(10, x) % 10); + +} + +#endregion 计算函数 + +#region 位置序列函数 +internal 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); + } + + public static bool IsValidPartyIndex(this ScriptAccessory sa, int index) + { + return index >= 0 && index < sa.Data.PartyList.Count; + } + + /// + /// 输入玩家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]; + } +} +#endregion 位置序列函数 + +#region 绘图函数 + +internal static class DrawTools +{ + private static CancellationTokenSource _lifecycleCts = new(); + + public static void ResetLifecycle() + { + _lifecycleCts.Cancel(); + _lifecycleCts.Dispose(); + _lifecycleCts = new CancellationTokenSource(); + } + + public static async void TextInfo(this ScriptAccessory sa, string msg, + int delayMs = 0, int destroyMs = 3000, bool isWarning = false) + { + var token = _lifecycleCts.Token; + try + { + await Task.Delay(delayMs, token); + sa.Method.TextInfo(msg, destroyMs, isWarning); + } + catch (OperationCanceledException) + { + } + } + + public static async void TTS(this ScriptAccessory sa, string msg, + int delayMs = 0, int voiceSpeed = 3) + { + var token = _lifecycleCts.Token; + try + { + await Task.Delay(delayMs, token); + sa.Method.TTS(msg, voiceSpeed); + } + catch (OperationCanceledException) + { + } + } + + /// + /// 返回绘图 + /// + /// + /// 绘图基准,可为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, Vector4 color, + 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 = color; + 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, + Vector4 color, float rotation = 0, float width = 1f, bool draw = true, bool useImgui = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, + DrawTypeEnum.Displacement, color, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, Vector4 color, float rotation = 0, float width = 1f, + bool draw = true, bool useImgui = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, color, rotation, width, draw, useImgui); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, Vector4 color, bool byTime = false, bool draw = true, bool useImgui = false) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, scale, scale, + 0, 0, useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, DrawTypeEnum.Circle, color, byTime,false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, Vector4 color, bool byTime = false, bool draw = true, bool useImgui = false) + => sa.DrawOwnerBase(ownerObj, 0, delay, destroy, name, 2 * float.Pi, 0, outScale, outScale, innerScale, + innerScale, useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, + DrawTypeEnum.Donut, color, 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, Vector4 color, bool byTime = false, bool draw = true, bool useImgui = false) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, + innerScale == 0 ? DrawTypeEnum.Fan : DrawTypeEnum.Donut, color, 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, Vector4 color, bool byTime = false, bool draw = true, bool useImgui = false) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, + color, byTime, draw, useImgui); + + /// + /// 返回矩形绘图 + /// + /// + /// 矩形起始 + /// 目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 矩形宽度 + /// 矩形长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, Vector4 color, bool byTime = false, bool byY = false, + bool draw = true, bool useImgui = false) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, length, 0, 0, + useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, DrawTypeEnum.Rect, color, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, Vector4 color, bool byTime = false, bool byY = false, + bool draw = true, bool useImgui = false) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, color, byTime, byY, draw, useImgui); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + Vector4 color, bool draw = true, bool useImgui = false) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, DrawTypeEnum.Displacement, color, 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, Vector4 color, bool byTime = false, bool byY = false, + bool draw = true, bool useImgui = false) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, DrawTypeEnum.Line, color, byTime, byY, draw); + + /// + /// 返回箭头绘图 + /// + /// + /// 箭头起始 + /// 箭头目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 箭头宽度 + /// 箭头长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawArrow(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, Vector4 color, + bool byTime = false, bool byY = false, bool draw = true, bool useImgui = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 1, rotation, width, length, 0, 0, + useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, DrawTypeEnum.Arrow, color, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, Vector4 color, float width = 1f, bool draw = true, bool useImgui = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, useImgui ? DrawModeEnum.Imgui : DrawModeEnum.Default, DrawTypeEnum.Line, color, false, true, draw); + + /// + /// 赋予输入的dp以仇恨顺序绘图 + /// + /// + /// 获得目标赋值给owner + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetEnmityOrder(this DrawPropertiesEdit self, bool setOwner, uint orderIdx) + { + if (setOwner) + { + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.CentreOrderIndex = orderIdx; + } + else + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.TargetOrderIndex = orderIdx; + } + + return self; + } + + /// + /// 赋予输入的dp以owner目标为源的绘图 + /// + /// + /// 获得目标赋值给owner + /// + public static DrawPropertiesEdit SetOwnerTarget(this DrawPropertiesEdit self, bool setOwner) + { + if (setOwner) + self.CentreResolvePattern = PositionResolvePatternEnum.OwnerTarget; + else + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + return self; + } + + /// + /// 在指定位置添加Omen + /// + /// + /// 生成位置 + /// omenID + /// 延时 + /// 消失时间 + /// omen缩放倍数 + /// omen颜色 + /// omen旋转弧度 + /// 播放速度 + /// + public static void DrawOmen(this ScriptAccessory sa, Vector3 position, uint omenId, + int delayMs, int destroyMs, Vector3 omenScale, Vector4? color = null, float rotation = 0f, float speed = 1f) + { + Task.Delay(Math.Max(50, delayMs)).ContinueWith(t => + { + var handle = sa.Method.VfxMethod.CreateOmen(omenId, + omenScale, position, rotation, + color ?? Vector4.One, destroyAt: destroyMs); + sa.Method.RunOnMainThreadAsync(() => + { + sa.Method.VfxMethod.SetVfxSpeed(handle, speed); + }); + }); + } + + /// + /// 在实体上添加Lockon + /// + /// + /// 实体 + /// 头标ID + /// 延时 + /// 消失时间 + /// 头标缩放倍数 + /// 播放速度 + /// + public static void DrawLockOn(this ScriptAccessory sa, ulong objectId, uint lockonId, + int delayMs, int destroyMs, Vector3 iconScale, float speed = 1) + { + Task.Delay(Math.Max(50, delayMs)).ContinueWith(t => + { + var handle = sa.Method.VfxMethod.CreateLockOn(lockonId, objectId, Vector4.One, destroyAt: destroyMs, + (ref Vector4 color, ref Vector4 pos, ref Vector3 scale) => + { + scale = iconScale; + }); + sa.Method.RunOnMainThreadAsync(() => + { + sa.Method.VfxMethod.SetVfxSpeed(handle, speed); + }); + }); + } + + /// + /// 在指定位置添加Lockon + /// + /// + /// 生成位置 + /// 头标ID + /// 延时 + /// 消失时间 + /// 生成虚拟实体ID偏置 + /// 头标缩放倍数 + /// 播放速度 + /// + public static void DrawLockOn(this ScriptAccessory sa, Vector3 position, uint lockonId, + int delayMs, int destroyMs, Vector3 iconScale, uint objIdBias = 0, float speed = 1) + { + var virtualObjectId = 0x40001234u + objIdBias; + var objHandle = sa.Method.ObjectMethod.CreateEmptyChara(virtualObjectId); + + Task.Delay(Math.Max(50, delayMs)).ContinueWith(t => + { + var handle = sa.Method.VfxMethod.CreateLockOn(lockonId, virtualObjectId, Vector4.One, destroyAt: destroyMs, + (ref Vector4 color, ref Vector4 pos, ref Vector3 scale) => + { + scale = iconScale; + unsafe + { + var obj = (BattleChara*)objHandle; + obj->Position = position; + } + }); + sa.Method.RunOnMainThreadAsync(() => + { + sa.Method.VfxMethod.SetVfxSpeed(handle, speed); + }); + Task.Delay(destroyMs).ContinueWith(t => + { + if (sa.GetById(virtualObjectId) is not { } obj) return; + sa.Method.ObjectMethod.DestoryChara(objHandle); + }); + }); + } + + public static void DrawCountDown(this ScriptAccessory sa, ulong objectId, + int delayMs, float iconScale = 2f, float speed = 1) + => sa.DrawLockOn(objectId, 184, delayMs, (int)(6000f / speed), new Vector3(iconScale, iconScale, iconScale), + speed); + + public static void DrawCountDown(this ScriptAccessory sa, Vector3 position, + int delayMs, float iconScale = 2f, uint objIdBias = 0, float speed = 1) + => sa.DrawLockOn(position, 184, delayMs, (int)(6000f / speed), new Vector3(iconScale, iconScale, iconScale), objIdBias, speed); + + public static void DrawLaser(this ScriptAccessory sa, Vector3 position, + int delayMs, int destroyMs, Vector3 omenScale, Vector4? color = null) + => sa.DrawOmen(position, 358, delayMs, destroyMs, omenScale, color ?? Vector4.One); +} + +#endregion 绘图函数 + +#region 调试函数 + +/// +/// 调试输出函数集,所有输出经由有序缓冲区,按 order 优先级统一排列后输出。 +/// +internal static class DebugFunction +{ + private static readonly List PrefixWhiteList = [""]; + private static readonly List PrefixBlackList = [""]; + + /// + /// 有序输出缓冲区的单条记录。 + /// + private record Entry(int Order, long Seq, string Content, bool ShowInChatBox); + + private static readonly List _buffer = new(); + private static long _seq; + private static CancellationTokenSource? _cts; + private static readonly object _lock = new(); + + public static void DebugMsg(this ScriptAccessory sa, string msg, + int order = 0, + [CallerMemberName] string prefix = "", + bool showInChatBox = true, bool enableWhiteList = false, bool enableBlackList = true, + int flushDelayMs = 200, bool immediate = false) + { + if (!UcobReborn.Debugging) return; + + if (enableWhiteList) + if (!PrefixWhiteList.Contains(prefix)) return; + if (enableBlackList) + if (PrefixBlackList.Contains(prefix)) return; + + var content = $"[{prefix}] {msg}"; + + if (immediate) + { + sa.Log.Debug(content); + if (showInChatBox) + sa.Method.SendChat($"/e {content}"); + return; + } + + lock (_lock) + { + _buffer.Add(new Entry(order, _seq++, content, showInChatBox)); + + _cts?.Cancel(); + _cts = new CancellationTokenSource(); + var token = _cts.Token; + + Task.Delay(flushDelayMs, token).ContinueWith(t => + { + if (t.IsCanceled) return; + FlushInternal(sa); + }); + } + } + + public static void FlushDebugMsg(this ScriptAccessory sa) + { + FlushInternal(sa); + } + + public static void ClearDebugMsg(this ScriptAccessory sa) + { + lock (_lock) + { + _cts?.Cancel(); + _buffer.Clear(); + } + } + + private async static void FlushInternal(ScriptAccessory sa) + { + List sorted; + lock (_lock) + { + if (_buffer.Count == 0) return; + sorted = _buffer.OrderBy(x => x.Order).ThenBy(x => x.Seq).ToList(); + _buffer.Clear(); + } + + foreach (var entry in sorted) + { + sa.Log.Debug(entry.Content); + if (entry.ShowInChatBox) + sa.Method.SendChat($"/e {entry.Content}"); + await Task.Delay(20); + } + } +} + +#endregion 调试函数 + +#region 特殊函数 + +internal static class SpecialFunction +{ + public static unsafe void AlphaModify(this ScriptAccessory sa, IGameObject? obj, float alpha, + Func? shouldModify = null) + { + alpha = Math.Clamp(alpha, 0f, 1f); + sa.Method.RunOnMainThreadAsync(Action); + void Action() + { + if (obj == null) return; + + Character* charaStruct = (Character*)obj.Address; + if (!obj.IsValid() || !charaStruct->IsReadyToDraw()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + + if (!charaStruct->IsCharacter()) + { + sa.Log.Error($"传入的IGameObject不是Character,无法修改透明度。"); + return; + } + + if (shouldModify != null && !shouldModify(charaStruct -> Alpha)) + return; + + charaStruct->Alpha = alpha; + sa.DebugMsg($"AlphaModify => {obj.Name.TextValue} | {obj} => {alpha}"); + } + } + + public static unsafe void Redraw(this ScriptAccessory sa, IGameObject? obj) + { + sa.Method.RunOnMainThreadAsync(Action); + void Action() + { + if (obj == null) return; + GameObject* charaStruct = (GameObject*)obj.Address; + if (!obj.IsValid() || !charaStruct->IsReadyToDraw()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + + charaStruct->DisableDraw(); + charaStruct->EnableDraw(); + } + } +} + +#endregion 特殊函数 + +#endregion 函数集 + diff --git a/lianying1997/06-EndWalker/ASS.cs b/lianying1997/06-EndWalker/ASS.cs new file mode 100644 index 0000000..2b2ce1e --- /dev/null +++ b/lianying1997/06-EndWalker/ASS.cs @@ -0,0 +1,2353 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Data; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Extensions; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs; + +namespace UsamisScript.EndWalker.ASS; + +[ScriptType(name: "ASS [异闻希拉狄哈水道]", territorys: [1075, 1076], guid: "bdd73dbd-2a93-4232-9324-0c9093d4a646", version: "0.0.1.5", author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +public class ASS +{ + private const string NoteStr = + """ + 请先按需求检查并设置“用户设置”栏目, + 并在/KTeam中设置 T>H>近>远。 + """; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + private const bool Debugging = false; + [UserSetting("站位提示圈绘图-普通颜色")] + public static ScriptColor posColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public static ScriptColor posColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("Boss1 泡泡危险范围颜色使用默认设置")] + public static bool Boss1_DefaultDangerColor { get; set; } = false; + + [UserSetting("Boss1 泡泡危险范围颜色设置")] + public static ScriptColor Boss1_SetDangerColor { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 0.0f, 1.0f, 1.0f) }; + + [UserSetting("Boss3 场地危险范围颜色使用默认设置")] + public static bool Boss3_DefaultDangerColor { get; set; } = false; + + [UserSetting("Boss3 场地危险范围颜色设置")] + public static ScriptColor Boss3_SetDangerColor { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0.0f, 1.0f) }; + public enum ASS_Phase + { + // TODO:存在的问题是,团灭后,副本不会重置!Init函数不奏效! + // TODO:需要找到一个方法,检测到团灭,然后重新执行Init + + Init, // 初始 + BOSS1_P2, // BOSS1 一染 + BOSS1_P3, // BOSS1 一拉 + BOSS1_P4, // BOSS1 二染 + BOSS1_P5, // BOSS1 二拉 + BOSS3_P1, // BOSS3 咒具一 + BOSS3_P2, // BOSS3 咒具二 + BOSS3_P3, // BOSS3 咒具三 + BOSS3_P4, // BOSS3 咒具四 + BOSS3_P5, // BOSS3 咒具五 + } + ASS_Phase phase = ASS_Phase.Init; + List Drawn = new bool[20].ToList(); // 绘图记录 + static Vector3 CENTER_BOSS1 = new Vector3(-335f, 471f, -155f); // BOSS1 场地中心 + static Vector3 CENTER_BOSS2 = new Vector3(-35f, 521f, -271f); // BOSS2 场地中心 + static Vector3 CENTER_BOSS3 = new Vector3(289f, 533f, -105f); // BOSS3 场地中心 + const int FIELDX_BOSS3 = 30; // BOSS3 场地X总长 + const int FIELDZ_BOSS3 = 40; // BOSS3 场地Z总长 + + uint Boss1_BubbleProperty = 0; // BOSS1 鼠鼠的泡泡属性 + List Boss1_P2_FieldBubbleProperty = [0, 0, 0]; // BOSS1 P2一染,场地泡泡属性记录 + List Boss1_P2_FieldBubbleSid = [0, 0, 0]; // BOSS1 P2一染,场地泡泡ID记录 + List Boss1_P3_FieldBubbleProperty = [0, 0, 0, 0]; // BOSS1 P3一拉,场地泡泡属性记录 + List Boss1_P3_FieldBubbleSid = []; // BOSS1 P3一拉,场地泡泡ID记录 + List Boss1_P4_WaterLine = new bool[4].ToList(); // BOSS1 P4二染,场地水壶记录 + + List Boss2_CurseBuff = [0, 0, 0, 0]; // BOSS2 记录分摊/天光轮回buff + List Boss2_isLongBuff = [false, false, false, false]; // BOSS2 记录长/短Buff + List Boss2_GoldenSilverBuff = [0, 0, 0, 0]; // BOSS2 记录金银Buff + List> Boss2_GoldenSilverField = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; // BOSS2 金银Buff场地计算 + + List Boss3_StrikeTarget = [0, 0]; // BOSS3 石火豪冲(被挡枪目标)记录 + List Boss3_P2_PlayerBuff = [0, 0, 0, 0]; // BOSS3 P2麻将记录 + List Boss3_P4_BrandRot = [0, 0, 0]; // BOSS3 咒具面向逻辑角度记录 + List Boss3_P4_PanelSid = [0, 0, 0]; // BOSS3 魔法阵ID记录 + int Boss3_P3_myRotateRow = 0; // BOSS3 旋转buff决定所在行 + List Boss3_P3_safeTeleportPos = + [ + new Vector3(0, 0, 0), + new Vector3(0, 0, 0), + new Vector3(0, 0, 0), + new Vector3(0, 0, 0) + ]; // BOSS3 咒具旋转安全位置 + List Boss3_P3_BaitPos = + [ + new Vector3(289f, 533f, -119.5f), + new Vector3(297f, 533f, -111.75f), + new Vector3(297f, 533f, -111.75f).FoldPointUD(CENTER_BOSS3.Z).FoldPointLR(CENTER_BOSS3.X), + new Vector3(289f, 533f, -119.5f).FoldPointUD(CENTER_BOSS3.Z), + ]; // BOSS3 引导斧头安全位置 + + public void Init(ScriptAccessory accessory) + { + ParamsInit(accessory); + } + + public void ParamsInit(ScriptAccessory accessory) + { + phase = ASS_Phase.Init; + Drawn = new bool[20].ToList(); + + Boss1_BubbleProperty = 0; // BOSS1 鼠鼠的泡泡属性 + Boss1_P2_FieldBubbleProperty = [0, 0, 0]; // BOSS1 P2一染,场地泡泡属性记录 + Boss1_P2_FieldBubbleSid = [0, 0, 0]; // BOSS1 P2一染,场地泡泡ID记录 + Boss1_P3_FieldBubbleProperty = [0, 0, 0, 0]; // BOSS1 P3一拉,场地泡泡属性记录 + Boss1_P3_FieldBubbleSid = []; // BOSS1 P3一拉,场地泡泡ID记录 + Boss1_P4_WaterLine = new bool[4].ToList(); // BOSS1 P4二染,场地水壶记录 + + Boss2_CurseBuff = [0, 0, 0, 0]; // BOSS2 记录分摊/天光轮回buff + Boss2_isLongBuff = [false, false, false, false]; // BOSS2 记录长/短Buff + Boss2_GoldenSilverBuff = [0, 0, 0, 0]; // BOSS2 记录金银Buff + Boss2_GoldenSilverField = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; // BOSS2 金银Buff场地计算 + + Boss3_StrikeTarget = [0, 0]; // BOSS3 石火豪冲(被挡枪目标)记录 + Boss3_P2_PlayerBuff = [0, 0, 0, 0]; // BOSS3 P2麻将记录 + Boss3_P4_BrandRot = [0, 0, 0]; // BOSS3 咒具面向逻辑角度记录 + Boss3_P4_PanelSid = [0, 0, 0]; // BOSS3 魔法阵ID记录 + Boss3_P3_myRotateRow = 0; // BOSS3 旋转buff决定所在行 + + Boss3_P3_safeTeleportPos = + [ + new Vector3(0, 0, 0), + new Vector3(0, 0, 0), + new Vector3(0, 0, 0), + new Vector3(0, 0, 0) + ]; // BOSS3 咒具旋转安全位置 + Boss3_P3_BaitPos = + [ + new Vector3(289f, 533f, -119.5f), + new Vector3(297f, 533f, -111.75f), + new Vector3(297f, 533f, -111.75f).FoldPointUD(CENTER_BOSS3.Z).FoldPointLR(CENTER_BOSS3.X), + new Vector3(289f, 533f, -119.5f).FoldPointUD(CENTER_BOSS3.Z), + ]; // BOSS3 引导斧头安全位置 + + accessory.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "测试项 - 获得玩家位置", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: Debugging)] + public void CheckPlayerRole(Event ev, ScriptAccessory sa) + { + sa.Log.Debug($"玩家位置:{sa.getMyIndex()}"); + } + + [ScriptMethod(name: "测试项 - 获得金银Buff场地", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: Debugging)] + public void CheckGoldenSilverField(Event ev, ScriptAccessory sa) + { + var a = Boss2_GoldenSilverField; + var str = ""; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + str += Boss2_GoldenSilverField[i][j].ToString(); + str += "\t"; + } + str += "\n"; + } + sa.Log.Debug($"---- 金银场地 ----\n{str}"); + } + + [ScriptMethod(name: "测试项 - 获得金银Buff站位", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: Debugging)] + public void CheckGoldenSilverSafeField(Event ev, ScriptAccessory sa) + { + int[] high = findGoldenSilverIndex(11, true); + int[] low = findGoldenSilverIndex(11, false); + sa.Log.Debug($"高优先级金银 {high[0]}行{high[1]}列;低优先级金银 {low[0]}行{low[1]}列"); + } + + [ScriptMethod(name: "团灭初始化", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:regex:^(1483[47]|14757|14864|14762|14817)$"], userControl: Debugging, suppress: 3000)] + public void WipeInit(Event @event, ScriptAccessory accessory) + { + var bossId = @event.DataId(); + const uint nSilkie = 14834; + const uint sSilkie = 14837; + const uint nGladiator = 14757; + const uint sGladiator = 14864; + const uint nShadowCaster = 14762; + const uint sShadowCaster = 14817; + + var bossName = bossId switch + { + nSilkie => "Boss1 鼠鼠", + sSilkie => "Boss1 零式鼠鼠", + nGladiator => "Boss2 守护者", + sGladiator => "Boss2 零式守护者", + nShadowCaster => "Boss3 影火", + sShadowCaster => "Boss3 零式影火", + _ => "未知" + }; + + ParamsInit(accessory); + accessory.Log.Debug($"检测到{bossName}的刷新,参数初始化。"); + } + + #region Mob1 + + [ScriptMethod(name: "Mob1:树人", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3107[567]|31099|3110[01])$"])] + public void Mob1_Kaluk(Event @event, ScriptAccessory accessory) + { + HashSet LEFT_CLEAVE = [31076, 31100]; + HashSet RIGHT_CLEAVE = [31075, 31099]; + HashSet FRONT_CLEAVE = [31077, 31101]; + + var sid = @event.SourceId(); + var aid = @event.ActionId(); + + var _scale = FRONT_CLEAVE.Contains(aid) ? 10 : 30; + var _radian = FRONT_CLEAVE.Contains(aid) ? float.Pi / 180 * 90 : float.Pi / 180 * 210; + var _rotation = FRONT_CLEAVE.Contains(aid) ? 0 : + LEFT_CLEAVE.Contains(aid) ? float.Pi / 180 * 75 : -float.Pi / 180 * 75; + + var dp = accessory.drawFan(sid, _radian, _scale, 250, 3750, $"树人刀"); + // dp.Rotation正值为逆时针 + dp.Rotation = _rotation; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Mob1:花", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3107[23]|3109[67])$"])] + public void Mob1_Belladonna(Event @event, ScriptAccessory accessory) + { + HashSet DONUT = [31072, 31096]; + HashSet SIGHT = [31073, 31097]; + + var sid = @event.SourceId(); + var aid = @event.ActionId(); + + if (DONUT.Contains(aid)) + { + var dp = accessory.drawDonut(sid, 0, 4000, $"花月环"); + dp.InnerScale = new(9f); + dp.Scale = new(40f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + else if (SIGHT.Contains(aid)) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"花背对"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = accessory.Data.Me; + dp.TargetObject = sid; + dp.Delay = 0; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.SightAvoid, dp); + } + else return; + } + + [ScriptMethod(name: "Mob1:三头龙", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3106[789]|3109[123])$"])] + public void Mob1_Udumbara(Event @event, ScriptAccessory accessory) + { + HashSet LEFT_CLEAVE = [31067, 31091]; + HashSet RIGHT_CLEAVE = [31068, 31092]; + HashSet FRONT_CLEAVE = [31069, 31093]; + + var sid = @event.SourceId(); + var aid = @event.ActionId(); + + var _scale = 30; + var _radian = FRONT_CLEAVE.Contains(aid) ? float.Pi / 180 * 120 : float.Pi / 180 * 180; + var _rotation = FRONT_CLEAVE.Contains(aid) ? 0 : + LEFT_CLEAVE.Contains(aid) ? float.Pi / 180 * 135 : -float.Pi / 180 * 135; + + var dp = accessory.drawFan(sid, _radian, _scale, 250, 3750, $"三头龙刀"); + dp.Rotation = _rotation; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Mob1:大树", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31063|31087)$"])] + public void Mob1_Dryad(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var _scale = 12; + var dp = accessory.drawCircle(sid, 0, 4250, $"大树钢铁"); + dp.Scale = new(_scale); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion + + #region BOSS1 鼠鼠 + + [ScriptMethod(name: "BOSS1:阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30566|30601)$"])] + public void Boss1_PhaseRecord(Event @event, ScriptAccessory accessory) + { + phase = phase switch + { + ASS_Phase.Init => ASS_Phase.BOSS1_P2, + ASS_Phase.BOSS1_P2 => ASS_Phase.BOSS1_P3, + ASS_Phase.BOSS1_P3 => ASS_Phase.BOSS1_P4, + ASS_Phase.BOSS1_P4 => ASS_Phase.BOSS1_P5, + _ => ASS_Phase.BOSS1_P2 + }; + accessory.Log.Debug($"当前阶段为:{phase}"); + } + + [ScriptMethod(name: "BOSS1:泡泡范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3055[567]|3056[89]|30570|3059[012]|3060[345])$"])] + public void Boss1_Bubbles(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var aid = @event.ActionId(); + + HashSet LIGHT_AIDS = [30557, 30570, 30592, 30605]; + HashSet WIND_AIDS = [30556, 30569, 30591, 30604]; + HashSet ICE_AIDS = [30555, 30568, 30590, 30603]; + + if (LIGHT_AIDS.Contains(aid)) + drawLightFan(sid, true, accessory); + if (WIND_AIDS.Contains(aid)) + drawWindDonut(sid, true, accessory); + if (ICE_AIDS.Contains(aid)) + drawIcePlusShaped(sid, true, accessory); + } + + private DrawPropertiesEdit drawLightFan(uint sid, bool draw, ScriptAccessory accessory) + { + var _scale = 60; + var _radian = float.Pi / 180 * 45; + var dp = accessory.drawFan(sid, _radian, _scale, 0, 5000, $"雷泡泡四角"); + dp.Color = Boss1_DefaultDangerColor ? accessory.Data.DefaultDangerColor : Boss1_SetDangerColor.V4; + if (draw) accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + return dp; + } + + private void drawLightSpread(uint sid, ScriptAccessory accessory) + { + var _scale = 5; + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + var dp = accessory.drawCircle(accessory.Data.PartyList[i], 6000, 3000, $"雷滑行分散"); + dp.Scale = new(_scale); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + List dirPosition = [2, 0, 1, 3]; + var myIndex = accessory.getMyIndex(); + for (int i = 0; i < dirPosition.Count(); i++) + { + var dp = accessory.drawRect(sid, 2, 10, 7000, 2000, $"分散位置指引{i}"); + dp.Rotation = dirPosition[i] * float.Pi / 2; + dp.Color = i == myIndex ? posColorPlayer.V4.WithW(1.5f) : posColorNormal.V4; + dp.Scale = new Vector2(4f, 20f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + } + + private DrawPropertiesEdit drawWindDonut(uint sid, bool draw, ScriptAccessory accessory) + { + var _scale = 60; + var _innerscale = 5; + var dp = accessory.drawDonut(sid, 0, 5000, $"风泡泡月环"); + dp.Color = Boss1_DefaultDangerColor ? accessory.Data.DefaultDangerColor : Boss1_SetDangerColor.V4; + dp.Scale = new(_scale); + dp.InnerScale = new(_innerscale); + if (draw) accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + return dp; + } + + private DrawPropertiesEdit[] drawIcePlusShaped(uint sid, bool draw, ScriptAccessory accessory) + { + var _scale = 10; + var _rotation = float.Pi / 180 * 90; + + var dp0 = accessory.drawRect(sid, _scale, 90, 0, 5000, $"冰泡泡十字"); + dp0.Color = Boss1_DefaultDangerColor ? accessory.Data.DefaultDangerColor : Boss1_SetDangerColor.V4; + dp0.Rotation = _rotation; + if (draw) accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp0); + + var dp1 = accessory.drawRect(sid, _scale, 90, 0, 5000, $"冰泡泡十字"); + dp1.Color = Boss1_DefaultDangerColor ? accessory.Data.DefaultDangerColor : Boss1_SetDangerColor.V4; + dp1.Rotation = 0; + if (draw) accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp1); + + return [dp0, dp1]; + } + + [ScriptMethod(name: "BOSS1:擦拭", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3054[56]|3058[01])$"])] + public void Boss1_SqueakyClean(Event @event, ScriptAccessory accessory) + { + // LEFT DOWN是左边安全,打右边 + HashSet LEFT_DOWN = [30545, 30580]; + HashSet RIGHT_DOWN = [30546, 30581]; + + var sid = @event.SourceId(); + var aid = @event.ActionId(); + + var _scale = 60; + var _radian = float.Pi / 180 * 225; + var _rotation = LEFT_DOWN.Contains(aid) ? -float.Pi / 180 * 67.5f : float.Pi / 180 * 67.5f; + + var dp = accessory.drawFan(sid, _radian, _scale, 0, 9000, $"擦拭"); + // dp.Rotation正值为逆时针 + dp.Rotation = _rotation; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + HashSet LIGHT_BUBBLE = [30553, 30588]; + HashSet ICE_BUBBLE = [30552, 30587]; + HashSet WIND_BUBBLE = [30551, 30586]; + [ScriptMethod(name: "BOSS1:泡泡属性记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3055[123]|3058[678])$"], userControl: false)] + public void Boss1_BubblePropertyRecord(Event @event, ScriptAccessory accessory) + { + var aid = @event.ActionId(); + Boss1_BubbleProperty = aid; + } + + [ScriptMethod(name: "BOSS1:泡泡滑行", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30558|30593)$"])] + public void Boss1_SlipperySoap(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + DrawPropertiesEdit?[] dps; + DrawTypeEnum _type; + + if (LIGHT_BUBBLE.Contains(Boss1_BubbleProperty)) + { + // 雷泡泡叉字后带分散 + dps = [drawLightFan(sid, false, accessory), null]; + _type = DrawTypeEnum.Fan; + drawLightSpread(sid, accessory); + } + else if (ICE_BUBBLE.Contains(Boss1_BubbleProperty)) + { + dps = drawIcePlusShaped(sid, false, accessory); + _type = DrawTypeEnum.Straight; + accessory.Method.TextInfo($"保持移动", 4000, true); + } + else if (WIND_BUBBLE.Contains(Boss1_BubbleProperty)) + { + // 风泡泡画月环是为了提醒击退 + dps = [drawWindDonut(sid, true, accessory), null]; + _type = DrawTypeEnum.Donut; + } + else + return; + + for (int i = 0; i < 2; i++) + { + if (dps[i] == null) continue; + dps[i].Delay = 6000; + dps[i].DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, _type, dps[i]); + } + } + + [ScriptMethod(name: "BOSS1:一染色球名称记录", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:regex:^(4561)$", "SourceDataId:regex:^(1483[58])$"], userControl: false)] + public void Boss1_P2_BubbleSidRecord(Event @event, ScriptAccessory accessory) + { + var spos = @event.SourcePosition(); + var sid = @event.SourceId(); + + if (phase == ASS_Phase.BOSS1_P2) + { + if (spos.X < CENTER_BOSS1.X - 5) + Boss1_P2_FieldBubbleSid[0] = sid; + else if (spos.X > CENTER_BOSS1.X + 5) + Boss1_P2_FieldBubbleSid[2] = sid; + else + Boss1_P2_FieldBubbleSid[1] = sid; + } + + if (phase == ASS_Phase.BOSS1_P3) + { + Boss1_P3_FieldBubbleSid.Add(sid); + } + + } + + [ScriptMethod(name: "BOSS1:一染色提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(330[567])$"])] + public async void Boss1_P2_PrintBubble(Event @event, ScriptAccessory accessory) + { + const uint WIND = 3305; + const uint ICE = 3306; + const uint LIGHT = 3307; + + if (phase != ASS_Phase.BOSS1_P2) return; + await Task.Delay(500); // 等待SID记录 + + var tid = @event.TargetId(); + if (!Boss1_P2_FieldBubbleSid.Contains(tid)) return; + var _idx = Boss1_P2_FieldBubbleSid.IndexOf(tid); + + var stid = @event.StatusID(); + Boss1_P2_FieldBubbleProperty[_idx] = stid; + + await Task.Delay(100); // 等待集合数值改变 + if (Boss1_P2_FieldBubbleProperty.Count(x => x == WIND) != 2) return; + + var _spec_idx = Boss1_P2_FieldBubbleProperty.FindIndex(x => x != WIND); + int _tidx; + if (Boss1_P2_FieldBubbleProperty[_spec_idx] == ICE) + // 剩冰球,去下半场另一只染色 + _tidx = Math.Abs(2 - _spec_idx); + else + // 剩电球,去上半场染色 + _tidx = 1; + + var dp = accessory.drawRect(Boss1_P2_FieldBubbleSid[_tidx], 10, 20, 0, 10000, $"一染色提示"); + dp.TargetPosition = CENTER_BOSS1; + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "BOSS1:一拉球记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(330[567])$"], userControl: false)] + public async void Boss1_P3_GrabBubbleRecord(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS1_P3) return; + await Task.Delay(500); // 等待SID记录 + + var tid = @event.TargetId(); + if (!Boss1_P3_FieldBubbleSid.Contains(tid)) return; + var _idx = Boss1_P3_FieldBubbleSid.IndexOf(tid); + + var stid = @event.StatusID(); + Boss1_P3_FieldBubbleProperty[_idx] = stid; + } + + [ScriptMethod(name: "BOSS1:一拉球提示", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(00D8)$"])] + public void Boss1_P3_GrabBubble(Event @event, ScriptAccessory accessory) + { + const uint WIND = 3305; + const uint ICE = 3306; + const uint LIGHT = 3307; + if (phase != ASS_Phase.BOSS1_P3) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + + var sid = @event.SourceId(); + var spos = @event.SourcePosition(); + + // 泡泡们默认朝向为下 + + // 先确认与自己相连的泡泡属性 + var _bubble_idx = Boss1_P3_FieldBubbleSid.IndexOf(sid); + bool _is_light_bubble = Boss1_P3_FieldBubbleProperty[_bubble_idx] == LIGHT; + + if (_is_light_bubble) + // 电球,无脑向外指 + grabBubbleToOut(spos, 0, 10000, accessory); + else + { + // 冰球,看BOSS + if (ICE_BUBBLE.Contains(Boss1_BubbleProperty)) + // 如果BOSS是冰球,无脑向外指 + grabBubbleToOut(spos, 0, 10000, accessory); + else + { + // 如果BOSS是电球,视位置拉 + grabBubbleToPos(spos, 0, 10000, accessory); + } + } + } + + private void grabBubbleToOut(Vector3 bubble_pos, int delay, int destory, ScriptAccessory accessory) + { + var _dir = bubble_pos.PositionRoundToDirs(CENTER_BOSS1, 8); + var _rot_radian = _dir * float.Pi / 180 * 45; + var _spos = bubble_pos.ExtendPoint(_rot_radian, 5); + var dp0 = accessory.drawStatic(_spos, 0, delay, destory, $"一拉球提示"); + dp0.Color = posColorPlayer.V4; + dp0.Scale = new(1f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + + var dp = accessory.dirPos(_spos, delay, destory, $"一拉球指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private void grabBubbleToPos(Vector3 bubble_pos, int delay, int destory, ScriptAccessory accessory) + { + var _dir = bubble_pos.PositionRoundToDirs(CENTER_BOSS1, 8); + // 不唯一,但简单 + int[] _rot_dir = [2, 2, 0, 2, 2, 6, 0, 0]; + var _rot_radian = _rot_dir[_dir] * float.Pi / 180 * 45; + var _spos = bubble_pos.ExtendPoint(_rot_radian, 5); + var dp0 = accessory.drawStatic(_spos, 0, delay, destory, $"一拉球提示"); + dp0.Color = posColorPlayer.V4; + dp0.Scale = new(1f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + + var dp = accessory.dirPos(_spos, delay, destory, $"一拉球指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "BOSS1:一拉球提示移除", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30(577|612|656))$"], userControl: false)] + public void Boss1_P3_GrabBubbleRemove(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS1_P3) return; + accessory.Method.RemoveDraw($"一拉球提示"); + accessory.Method.RemoveDraw($"一拉球指路"); + } + + [ScriptMethod(name: "BOSS1:二染水壶位置记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30(574|609))$"], userControl: false)] + public void Boss1_P4_WaterRecord(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS1_P4) return; + var spos = @event.SourcePosition(); + // -347, -339, [-335], -331, -323, + // -43, -42, -41, -40 + int _idx = (int)(spos.X / 8) + 43; + accessory.Log.Debug($"检测到水壶出现在第{_idx + 1}列"); + Boss1_P4_WaterLine[_idx] = true; + } + + [ScriptMethod(name: "BOSS1:二染死刑位置提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30(543|578))$"])] + public void Boss1_P4_TankBusterDir(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS1_P4) return; + if (accessory.Data.MyObject is not { } o || !o.IsTank()) return; + // 没有被清掉的一侧为少 + var _isleft = Boss1_P4_WaterLine.IndexOf(false) > 1; + accessory.Log.Debug($"第{Boss1_P4_WaterLine.IndexOf(false)}列被移除,T需去{(_isleft ? "左" : "右")}引导。"); + Vector3 _pos = new Vector3(-350, 471, -155); // 左侧 + if (!_isleft) + _pos = _pos.FoldPointLR(CENTER_BOSS1.X); + + var dp0 = accessory.drawStatic(_pos, 0, 0, 10000, $"二染死刑位置提示"); + dp0.Color = posColorPlayer.V4; + dp0.Scale = new(1f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + + var dp = accessory.dirPos(_pos, 0, 10000, $"二染死刑位置指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "BOSS1:二染死刑位置提示移除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(30(543|578))$"])] + public void Boss1_P4_TankBusterDirRemove(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS1_P4) return; + accessory.Method.RemoveDraw($"二染死刑位置提示"); + accessory.Method.RemoveDraw($"二染死刑位置指路"); + } + + #endregion + + #region Mob2 + + [ScriptMethod(name: "Mob2:瘦子", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31083|31107)$"])] + public void Mob2_Armor(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + // 此处月环意为:续剑扇形,要么出去,要么靠近 + var _scale = 12; + var _innerscale = 2.5f; + var dp = accessory.drawDonut(sid, 0, 5000, $"瘦子加重"); + dp.Scale = new(_scale); + dp.InnerScale = new(_innerscale); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + + [ScriptMethod(name: "Mob2:胖子", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31078|31102)$"])] + public void Mob2_Dullahan(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var _scale = 10; + var dp = accessory.drawCircle(sid, 0, 4000, $"胖子钢铁"); + dp.Scale = new(_scale); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion + + #region BOSS2 斗士 + + [ScriptMethod(name: "Boss2:冲锋前后刀", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3029[678]|3061[89]|30620)$"])] + public void Boss2_RushCleave(Event @event, ScriptAccessory accessory) + { + HashSet ONE = [30296, 30618]; + HashSet TWO = [30297, 30619]; + HashSet THREE = [30298, 30620]; + + var aid = @event.ActionId(); + var spos = @event.SourcePosition(); + var srot = @event.SourceRotation(); + + var _rushDistance = ONE.Contains(aid) ? 20f : + TWO.Contains(aid) ? 27.5f : + THREE.Contains(aid) ? 35f : 20f; + + var _radian = @event.SourceRotation().BaseInnGame2DirRad(); + var _target_pos = spos.ExtendPoint(_radian, _rushDistance); + + var dp = accessory.drawStatic(_target_pos, 0, 0, 10500, $"冲锋刀1"); + dp.Scale = new(60, 60); + dp.Rotation = srot; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + var dp2 = accessory.drawStatic(_target_pos, 0, 10500, 2000, $"冲锋刀2"); + dp2.Scale = new(60, 60); + dp2.Rotation = srot + float.Pi; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp2); + } + + [ScriptMethod(name: "Boss2:挡枪分摊提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30316|30638)$"])] + public void Boss2_WildCharge(Event @event, ScriptAccessory accessory) + { + accessory.Method.TextInfo($"坦克挡枪分摊", 4000, true); + } + + const uint CURSE_SPREAD = 3290; + const uint CURSE_STACK = 3293; + const uint CURSE_BAIT = 3292; + + [ScriptMethod(name: "Boss2:残响Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(329[023])$"], userControl: false)] + public void Boss2_Curse_BuffRecord(Event @event, ScriptAccessory accessory) + { + var stid = @event.StatusID(); + var tid = @event.TargetId(); + var dur = @event.DurationMilliseconds(); + var tidx = accessory.getPlayerIdIndex(tid); + + Boss2_isLongBuff[tidx] = dur > 15000; + if (stid != CURSE_SPREAD) + Boss2_CurseBuff[tidx] = stid; + } + + [ScriptMethod(name: "Boss2:残响Buff绘图", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(329[023])$"])] + public async void Boss2_CurseDraw(Event @event, ScriptAccessory accessory) + { + var stid = @event.StatusID(); + var tid = @event.TargetId(); + var dur = @event.DurationMilliseconds(); + var _myIndex = accessory.getMyIndex(); + var _scale = stid switch + { + CURSE_SPREAD => 6, + CURSE_STACK => 5, + CURSE_BAIT => 5, + _ => 5, + }; + + await Task.Delay(100); + var _delay = (int)dur - 3000 - 100; + var _destory = 3000; + var dp = accessory.drawCircle(tid, _delay, _destory, $"残响{stid}"); + dp.Scale = new(_scale); + + if (stid == CURSE_BAIT) + dp.Color = ColorHelper.colorRed.V4.WithW(2f); + else + dp.Color = (Boss2_CurseBuff[_myIndex] != CURSE_BAIT && stid == CURSE_STACK) ? accessory.Data.DefaultSafeColor.WithW(2f) : accessory.Data.DefaultDangerColor.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Boss2:蓄力钢铁月环", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3030[123456]|3062[345678])$"])] + public void Boss2_ChariotDonut(Event @event, ScriptAccessory accessory) + { + HashSet CHARIOT_1 = [30301, 30623]; + HashSet CHARIOT_2 = [30302, 30624]; + HashSet CHARIOT_3 = [30303, 30625]; + + HashSet DONUT_1 = [30304, 30626]; + HashSet DONUT_2 = [30305, 30627]; + HashSet DONUT_3 = [30306, 30628]; + + var aid = @event.ActionId(); + var sid = @event.SourceId(); + DrawTypeEnum _type = CHARIOT_1.Contains(aid) || CHARIOT_2.Contains(aid) || CHARIOT_3.Contains(aid) ? + DrawTypeEnum.Circle : DrawTypeEnum.Donut; + + var _innerscale = CHARIOT_1.Contains(aid) || DONUT_1.Contains(aid) ? 8f : + CHARIOT_2.Contains(aid) || DONUT_2.Contains(aid) ? 13f : + CHARIOT_3.Contains(aid) || DONUT_3.Contains(aid) ? 18f : 8f; + + if (_type == DrawTypeEnum.Circle) + { + var dp = accessory.drawCircle(sid, 0, 10000, $"刚武旋击钢铁"); + accessory.Log.Debug($"刚武旋击钢铁 {_innerscale} 技能ID {aid}"); + dp.Scale = new(_innerscale); + dp.Color = accessory.Data.DefaultDangerColor.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, _type, dp); + } + else + { + var dp = accessory.drawDonut(sid, 10000, 2000, $"刚武旋击月环"); + accessory.Log.Debug($"刚武旋击月环 {_innerscale} 技能ID {aid}"); + dp.InnerScale = new(_innerscale); + dp.Scale = new(30f); + dp.Color = accessory.Data.DefaultDangerColor.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, _type, dp); + } + + List dirPosition = [0, 2, 3, 1]; + var myIndex = accessory.getMyIndex(); + for (int i = 0; i < dirPosition.Count(); i++) + { + var dp = accessory.dirPos2Pos(CENTER_BOSS2, CENTER_BOSS2.ExtendPoint(dirPosition[i] * float.Pi / 2, 20), 0, + 8000, $"十字站位指引{i}"); + dp.Color = i == myIndex ? posColorPlayer.V4.WithW(1.5f) : posColorNormal.V4; + dp.Scale = new Vector2(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + } + + HashSet GOLDEN_BEAM = [30319, 30641]; + HashSet SILVER_BEAM = [30320, 30642]; + const uint GOLDEN_BUFF = 3295; + const uint SILVER_BUFF = 3296; + + [ScriptMethod(name: "Boss2:金银Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(329[56])$"], userControl: false)] + public void Boss2_GoldenSilver_BuffRecord(Event @event, ScriptAccessory accessory) + { + var stid = @event.StatusID(); + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + var param = @event.Param(); + + // 此处金BUFF+1,银BUFF+10,与场地的金炮+10,银炮+1可反向对应 + int _dx = stid == GOLDEN_BUFF ? 1 : 10; + Boss2_GoldenSilverBuff[tidx] = Boss2_GoldenSilverBuff[tidx] + (int)param * _dx; + + if (stid != CURSE_SPREAD) + Boss2_CurseBuff[tidx] = stid; + } + + [ScriptMethod(name: "Boss2:金银射线", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(303(19|20)|3064[12])$"])] + public async void Boss2_GoldenSilverBeam(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var aid = @event.ActionId(); + var spos = @event.SourcePosition(); + var srot = @event.SourceRotation(); + + Vector4 _color = accessory.Data.DefaultDangerColor; + + if (Boss2_GoldenSilverBuff.All(x => x == 0)) + { + // 无金银Buff时 + // 显示时间无需过长,给九字切让路 + var dp = accessory.drawRect(sid, 10, 40, 0, 8400, $"射线{aid}"); + dp.Color = _color; + dp.Rotation = SILVER_BEAM.Contains(aid) ? float.Pi : 0; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + return; + } + + // 有金银Buff时 + bool _is_golden = GOLDEN_BEAM.Contains(aid); + // _color = _is_golden ? ColorHelper.colorYellow.V4 : ColorHelper.colorWhite.V4; + var _idx = calcGoldenSilverRowCol(spos); + var _face_dir = calcGoldenSilverFaceDir(srot); + calcGoldenSilverField(_idx, _face_dir, _is_golden); + + // 安全格高亮与指路 + if (Drawn[0]) return; + Drawn[0] = true; + await Task.Delay(100); + + var myIndex = accessory.getMyIndex(); + // 11代表金银各1 + accessory.Log.Debug($"根据顺序,金银Buff为:{Boss2_GoldenSilverBuff[0]}, {Boss2_GoldenSilverBuff[1]}, {Boss2_GoldenSilverBuff[2]}, {Boss2_GoldenSilverBuff[3]},"); + if (Boss2_GoldenSilverBuff[myIndex] != 11) + { + int[] _idxs = findGoldenSilverIndex(Boss2_GoldenSilverBuff[myIndex], true); + drawSafeSquare(_idxs[0], _idxs[1], accessory); + } + else + { + int[] _priority = [0, 3, 1, 2]; // T 近 远 奶 + var _my_prior = _priority[myIndex]; + var _other_idx = Boss2_GoldenSilverBuff.IndexOf(11) != myIndex ? Boss2_GoldenSilverBuff.IndexOf(11) : Boss2_GoldenSilverBuff.LastIndexOf(11); + var _other_prior = _priority[_other_idx]; + var _is_first = _my_prior < _other_prior; + accessory.Log.Debug($"我({myIndex})的优先级为 {_my_prior},另一人({_other_idx})的优先级为 {_other_prior},我isFirst {_is_first}"); + int[] _idxs = findGoldenSilverIndex(Boss2_GoldenSilverBuff[myIndex], _is_first); + drawSafeSquare(_idxs[0], _idxs[1], accessory); + } + } + + [ScriptMethod(name: "Boss2:隆起分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30348|30653)$"])] + public void Boss2_EarthSpread(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var dp = accessory.drawCircle(tid, 3000, 2000, $"隆起{tid}"); + dp.Scale = new(8f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "Boss2:大残响Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3291)$"], userControl: false)] + public void Boss2_HugeCurse_BuffRecord(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + var dur = @event.DurationMilliseconds(); + Boss2_isLongBuff[tidx] = dur > 20000; + } + + [ScriptMethod(name: "Boss2:大残响范围绘图", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3291)$"])] + public void Boss2_HugeCurseDraw(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var dur = @event.DurationMilliseconds(); + var _delay = dur - 4000; + var _destory = 4000; + + var dp = accessory.drawCircle(tid, (int)_delay, _destory, $"大残响{tid}"); + dp.Scale = new(15f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private int calcGoldenSilverRowCol(Vector3 pos) + { + // 1行 -35 521 -286 + // 2行 -35 521 -276 + // 3行 -35 521 -266 + // 4行 -35 521 -256 + + // 1列 -50 521 -271 + // 2列 -40 521 -271 + // 3列 -30 521 -271 + // 4列 -20 521 -271 + int idx; + bool isHorizon = Math.Abs(pos.Z - -271) < 1.5f; + if (isHorizon) + idx = (int)((pos.X + 51) / 10); + else + idx = (int)((pos.Z + 287) / 10); + return idx; + } + + private int calcGoldenSilverFaceDir(float rot) + { + int dir = (int)Math.Round(rot.BaseInnGame2DirRad() / (float.Pi / 2)); + return dir; + } + + /// + /// 计算金银激光对场地的影响 + /// + /// 第几行/第几列 + /// 面向方向(0上1右2下3左) + /// 是否为金炮 + private void calcGoldenSilverField(int idx, int face_dir, bool is_golden) + { + // 金+10,银+1,分别找11、20、2。 + if (face_dir % 2 == 0) + { + // 如果face_dir是偶数,必是水平雕像,影响竖格 + var _col = idx; + var _row = (face_dir + 2 * (is_golden ? 0 : 1)) % 4; + // [_row][_col], [_row+1][_col] + Boss2_GoldenSilverField[_row][_col] += is_golden ? 10 : 1; + Boss2_GoldenSilverField[_row + 1][_col] += is_golden ? 10 : 1; + } + else + { + var _row = idx; + var _col = (face_dir + 1 + 2 * (is_golden ? 0 : 1)) % 4; + // [_row][_col], [_row][_col+1] + Boss2_GoldenSilverField[_row][_col] += is_golden ? 10 : 1; + Boss2_GoldenSilverField[_row][_col + 1] += is_golden ? 10 : 1; + } + } + private void drawSafeSquare(int row, int col, ScriptAccessory accessory) + { + // 1行 -35 521 -286 + // 2行 -35 521 -276 + // 3行 -35 521 -266 + // 4行 -35 521 -256 + + // 1列 -50 521 -271 + // 2列 -40 521 -271 + // 3列 -30 521 -271 + // 4列 -20 521 -271 + var _x = -50 + col * 10; + var _z = -286 + row * 10; + Vector3 pos = new Vector3(_x, 521f, _z); + var dp = accessory.drawStatic(pos, 0, 0, 7500, $"金银射线安全{row}{col}"); + dp.Scale = new(10, 10); + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + + var dp0 = accessory.dirPos(pos, 0, 7500, $"金银射线指路{row}{col}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + + private int[] findGoldenSilverIndex(int value, bool is_first) + { + List> priority = [[30, 30, 0, 0], [30, 30, 0, 0], [20, 20, 10, 10], [20, 20, 10, 10]]; + int[] safeField1 = [4, 4]; + int[] safeField2 = [4, 4]; + for (int i = 0; i < Boss2_GoldenSilverField.Count; i++) + { + for (int j = 0; j < Boss2_GoldenSilverField[i].Count; j++) + { + if (Boss2_GoldenSilverField[i][j] != value) continue; + + if (value != 11) return [i, j]; + + if (safeField1.SequenceEqual([4, 4])) + safeField1 = [i, j]; + else + safeField2 = [i, j]; + } + } + + var isSf1HighPiror = priority[safeField1[0]][safeField1[1]] < priority[safeField2[0]][safeField2[1]] ? 1 : -1; + var isFirst = is_first ? 1 : -1; + + return isSf1HighPiror * isFirst > 0 ? safeField1 : safeField2; + } + + [ScriptMethod(name: "Boss2:连线集合提示绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30(310|632))$"])] + public void Boss2_TetherGatherFirstDraw(Event @event, ScriptAccessory accessory) + { + var dp = accessory.drawStatic(CENTER_BOSS2, 0, 0, 4000, $"连线集合"); + dp.Scale = new Vector2(1.5f); + dp.Color = posColorPlayer.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp0 = accessory.dirPos(CENTER_BOSS2, 0, 4000, $"连线集合指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + + [ScriptMethod(name: "Boss2:连线安全方向绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30(312|634))$"])] + public void Boss2_TetherSafePosDraw(Event @event, ScriptAccessory accessory) + { + var tpos = @event.TargetPosition(); + var v = new Vector2(tpos.X - CENTER_BOSS2.X, tpos.Z - CENTER_BOSS2.Z); + if (v.Length() < 5) return; + + if (Drawn[1]) return; + Drawn[1] = true; + var tetherDangerPos = tpos.PositionRoundToDirs(CENTER_BOSS2, 4); + // AC先出 + List safePos = []; + if (tetherDangerPos % 2 == 0) + { + // 初始安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * -45, 13 * float.Sqrt(2))); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 135, 13 * float.Sqrt(2))); + // 后续安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 0, 13)); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 180, 13)); + } + else + { + // 初始安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 45, 13 * float.Sqrt(2))); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * -135, 13 * float.Sqrt(2))); + // 后续安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 90, 13)); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 270, 13)); + } + + var myIndex = accessory.getMyIndex(); + DrawPropertiesEdit dp1; + DrawPropertiesEdit dp2; + if (myIndex % 2 == 0) + { + // 是T与近战,上半场 + dp1 = accessory.dirPos(safePos[0], 0, 10000, $"第一步"); + dp2 = accessory.dirPos2Pos(safePos[0], safePos[2], 0, 10000, $"第二步准备"); + } + else + { + // 是H与远程,上半场 + dp1 = accessory.dirPos(safePos[1], 0, 10000, $"第一步"); + dp2 = accessory.dirPos2Pos(safePos[1], safePos[3], 0, 10000, $"第二步准备"); + } + dp2.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + } + + [ScriptMethod(name: "Boss2:连线安全方向指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(30(312|634))$"])] + public void Boss2_TetherSafePosDir(Event @event, ScriptAccessory accessory) + { + var tpos = @event.TargetPosition(); + var v = new Vector2(tpos.X - CENTER_BOSS2.X, tpos.Z - CENTER_BOSS2.Z); + if (v.Length() < 5) return; + + if (Drawn[2]) return; + Drawn[2] = true; + var tetherDangerPos = tpos.PositionRoundToDirs(CENTER_BOSS2, 4); + // AC先出 + List safePos = []; + if (tetherDangerPos % 2 == 0) + { + // 初始安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * -45, 13 * float.Sqrt(2))); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 135, 13 * float.Sqrt(2))); + // 后续安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 0, 13)); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 180, 13)); + } + else + { + // 初始安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 45, 13 * float.Sqrt(2))); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * -135, 13 * float.Sqrt(2))); + // 后续安全点 + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 90, 13)); + safePos.Add(CENTER_BOSS2.ExtendPoint(float.Pi / 180 * 270, 13)); + } + + var myIndex = accessory.getMyIndex(); + DrawPropertiesEdit dp2; + if (myIndex % 2 == 0) + // 是T与近战,上半场 + dp2 = accessory.dirPos(safePos[2], 0, 2000, $"第二步"); + else + // 是H与远程,上半场 + dp2 = accessory.dirPos(safePos[3], 0, 2000, $"第二步"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + accessory.Method.RemoveDraw($"第一步"); + accessory.Method.RemoveDraw($"第二步准备"); + } + + [ScriptMethod(name: "Boss2:踩塔与放圈安全方向指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30(314|636))$"])] + public void Boss2_TowerAndChariotSafeDir(Event @event, ScriptAccessory accessory) + { + if (Drawn[3]) return; + Drawn[3] = true; + + var spos = @event.SourcePosition(); + var pos = spos.PositionRoundToDirs(CENTER_BOSS2,4); + List chariotPos = []; + List towerPos = []; + + if (pos % 2 == 0) + { + // 先上下塔,塔半径3 + // 近战右上,远程左下 + towerPos.Add(CENTER_BOSS2.ExtendPoint(0 * float.Pi / 2, 7.5f)); + towerPos.Add(CENTER_BOSS2.ExtendPoint(2 * float.Pi / 2, 7.5f)); + towerPos.Add(CENTER_BOSS2.ExtendPoint(1 * float.Pi / 2, 7.5f)); + towerPos.Add(CENTER_BOSS2.ExtendPoint(3 * float.Pi / 2, 7.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(1 * float.Pi / 2, 19.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(3 * float.Pi / 2, 19.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(0 * float.Pi / 2, 19.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(2 * float.Pi / 2, 19.5f)); + } + else + { + // 先左右塔,塔半径3 + // 近战右上,远程左下 + towerPos.Add(CENTER_BOSS2.ExtendPoint(1 * float.Pi / 2, 7.5f)); + towerPos.Add(CENTER_BOSS2.ExtendPoint(3 * float.Pi / 2, 7.5f)); + towerPos.Add(CENTER_BOSS2.ExtendPoint(0 * float.Pi / 2, 7.5f)); + towerPos.Add(CENTER_BOSS2.ExtendPoint(2 * float.Pi / 2, 7.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(0 * float.Pi / 2, 19.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(2 * float.Pi / 2, 19.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(1 * float.Pi / 2, 19.5f)); + chariotPos.Add(CENTER_BOSS2.ExtendPoint(3 * float.Pi / 2, 19.5f)); + } + + // 若同组内数字相同,dps需互换 + var swap = (Boss2_isLongBuff[0] && Boss2_isLongBuff[2]) || (Boss2_isLongBuff[1] && Boss2_isLongBuff[3]); + var myIndex = accessory.getMyIndex(); + + // isLongBuff影响先踩塔还是先放圈 + // playerIdx影响[0,2]还是[1,3] + List actionJudge = [false, false]; + // actionJudge[0]: true则先踩塔,false则先放圈 + // actionJudge[1]: true则视为近战组0-2,false则视为远程组1-3 + if (Boss2_isLongBuff[myIndex]) + actionJudge[0] = true; + if (myIndex % 2 == 0) + actionJudge[1] = true; + if (myIndex >= 2 && swap) + actionJudge[1] = !actionJudge[1]; + + accessory.Log.Debug($"我{(swap ? "要换" : "不换")},先{(actionJudge[0] ? "踩塔" : "放圈")},我是{(actionJudge[1] ? "右上" : "左下")}组。"); + + Vector3 tpos1 = (actionJudge[0] ? towerPos : chariotPos)[actionJudge[1] ? 0 : 1]; + Vector3 tpos2 = (actionJudge[0] ? chariotPos : towerPos)[actionJudge[1] ? 2 : 3]; + var dp1 = accessory.dirPos(tpos1, 0, 6500, $"第一步"); + var dp12 = accessory.dirPos2Pos(tpos1, tpos2, 0, 6500, $"第二步准备"); + dp12.Color = accessory.Data.DefaultDangerColor; + var dp2 = accessory.dirPos(tpos2, 6500, 4500, $"第二步"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp12); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + } + #endregion + + #region BOSS3 键山雏 + + [ScriptMethod(name: "Boss3:阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(29841)$"], userControl: false)] + public void Boss3_PhaseRecord(Event @event, ScriptAccessory accessory) + { + phase = phase switch + { + ASS_Phase.Init => ASS_Phase.BOSS3_P1, + ASS_Phase.BOSS3_P1 => ASS_Phase.BOSS3_P2, + ASS_Phase.BOSS3_P2 => ASS_Phase.BOSS3_P3, + ASS_Phase.BOSS3_P3 => ASS_Phase.BOSS3_P4, + ASS_Phase.BOSS3_P4 => ASS_Phase.BOSS3_P5, + _ => ASS_Phase.BOSS3_P1 + }; + accessory.Log.Debug($"当前阶段为:{phase}"); + } + + [ScriptMethod(name: "Boss3:顺劈死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(29869|30404)$"])] + public void Boss3_TankBuster(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var sid = @event.SourceId(); + + var dp = accessory.drawFan(sid, float.Pi / 180 * 60, 40, 0, 5000, $"顺劈{tid}"); + dp.TargetObject = tid; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "Boss3:石火豪冲", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(29872)$"])] + public void Boss3_Strike(Event @event, ScriptAccessory accessory) + { + var myIndex = accessory.getMyIndex(); + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + if (i == myIndex) continue; + var dp = accessory.drawCircle(accessory.Data.PartyList[i], 0, 5000, $"石火豪冲{i}"); + dp.Scale = new(10f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "Boss3:石火豪冲目标记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(2987[34]|3040[67])$"], userControl: false)] + public void Boss3_StrikeTargetRecord(Event @event, ScriptAccessory accessory) + { + if (@event.TargetIndex() != 1) return; + + const uint FIRST = 29873; + const uint SECOND = 29874; + const uint FIRST_SAVAGE = 30406; + const uint SECOND_SAVAGE = 30407; + + var aid = @event.ActionId(); + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + + var _idx = aid switch + { + FIRST or FIRST_SAVAGE => 0, + SECOND or SECOND_SAVAGE => 1, + _ => 0 + }; + + Boss3_StrikeTarget[_idx] = tidx; + } + + [ScriptMethod(name: "Boss3:祝福圣火挡枪", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(29875)$"])] + public void Boss3_BlessedBeacon(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var myIndex = accessory.getMyIndex(); + for (int i = 0; i < Boss3_StrikeTarget.Count(); i++) + { + var dp = accessory.drawRect(sid, 8, 65, 0, 7000, $"祝福圣火挡枪{Boss3_StrikeTarget[i]}"); + dp.TargetObject = accessory.Data.PartyList[Boss3_StrikeTarget[i]]; + dp.Color = Boss3_StrikeTarget.Contains(myIndex) ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + + [ScriptMethod(name: "Boss3:祝福圣火挡枪消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(2987[67]|3040[89])$"], userControl: false)] + public void Boss3_BlessedBeaconRemove(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + accessory.Method.RemoveDraw($"祝福圣火挡枪{tidx}"); + } + + const uint CLOCKWISE = 64; + const uint COUNTER_CLOCKWISE = 256; + + [ScriptMethod(name: "Boss3:咒具一转火球", eventType: EventTypeEnum.ObjectEffect, eventCondition: ["Id1:regex:^(64|256)$"])] + public void Boss3_P1_FireBall(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P1) return; + var spos = @event.SourcePosition(); + var id1 = @event.Id1(); + + // 出特效的地方一定是有火球的地方 + Vector3 CENTER_UP = new(289f, 533, -115f); + Vector3 CENTER_DOWN = new(289f, 533, -95f); + + Vector3 _array_center = spos.Z > CENTER_BOSS3.Z ? CENTER_DOWN : CENTER_UP; + var _pos = spos.PositionRoundToDirs(_array_center, 4); + var _rot_radian = id1 switch + { + CLOCKWISE => float.Pi / 2, + COUNTER_CLOCKWISE => -float.Pi / 2, + _ => float.Pi / 2 + }; + drawRotFireball(spos, _pos, _rot_radian, _array_center, true, 0, 10000, accessory); + } + + private DrawPropertiesEdit drawRotFireball(Vector3 spos, int pos, float rot_radian, Vector3 center, bool draw, int delay, int destory, ScriptAccessory accessory) + { + // 先找到对面 + var _mirror_pos = pos % 2 == 0 ? spos.FoldPointUD(center.Z) : spos.FoldPointLR(center.X); + // 再旋转 + var _tpos = _mirror_pos.RotatePoint(spos, rot_radian); + var dp = accessory.drawStatic(_tpos, 0, delay, destory, $"旋转火球{spos}"); + dp.Scale = new(12f); + dp.Color = Boss3_DefaultDangerColor ? accessory.Data.DefaultDangerColor.WithW(1.5f) : Boss3_SetDangerColor.V4.WithW(1.5f); + if (draw) accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + return dp; + } + + [ScriptMethod(name: "Boss3:咒具二立体魔法阵", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(14770|14821)$"])] + public void Boss3_P2_Panels(Event @event, ScriptAccessory accessory) + { + var spos = @event.SourcePosition(); + var srot = @event.SourceRotation(); + var sid = @event.SourceId(); + + if (phase == ASS_Phase.BOSS3_P2) + { + var _delay = 12000; + var _destory = 25000; + drawPanelStraight(sid, _delay, _destory, true, accessory); + } + else if (phase == ASS_Phase.BOSS3_P4) + { + int _idx; + if (spos.X < CENTER_BOSS3.X - 5) + _idx = 0; + else if (spos.X > CENTER_BOSS3.X + 5) + _idx = 2; + else + _idx = 1; + Boss3_P4_PanelSid[_idx] = sid; + } + else + return; + } + + private DrawPropertiesEdit drawPanelStraight(uint sid, int delay, int destory, bool draw, ScriptAccessory accessory) + { + var dp = accessory.drawRect(sid, 10, 100, delay, destory, $"圣火炮{sid}"); + dp.Color = Boss3_DefaultDangerColor ? accessory.Data.DefaultDangerColor.WithW(1f) : Boss3_SetDangerColor.V4.WithW(1f); + if (draw) accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + return dp; + } + + [ScriptMethod(name: "Boss3:圣火炮消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(29862|30401)$"], userControl: false)] + public void Boss3_PanelsRemove(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + accessory.Method.RemoveDraw($"圣火炮{sid}"); + } + + [ScriptMethod(name: "Boss3:咒具二麻将Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(32(6[89]|7[01]))$"], userControl: false)] + public void Boss3_P2_PlayerBuffRecord(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P2) return; + const uint one = 3268; + const uint two = 3269; + const uint three = 3270; + const uint four = 3271; + + var aid = @event.StatusID(); + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + Boss3_P2_PlayerBuff[tidx] = aid switch + { + one => 1, + two => 2, + three => 3, + four => 4, + _ => 0 + }; + + accessory.Log.Debug($"检测到玩家{tidx}的Buff为{Boss3_P2_PlayerBuff[tidx]}"); + } + + [ScriptMethod(name: "Boss3:咒具二剪线位置指路", eventType: EventTypeEnum.StatusAdd, eventCondition: ["Param:regex:^(45[01234567])$"])] + public async void Boss3_P2_CutLineDir(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P2) return; + HashSet one = [450, 454]; + HashSet two = [451, 455]; + HashSet three = [452, 456]; + HashSet four = [453, 457]; + + var param = @event.Param(); + var tid = @event.TargetId(); + var tpos = @event.TargetPosition(); + + await Task.Delay(100); + var myIndex = accessory.getMyIndex(); + accessory.Log.Debug($"检测到线{tid}的Buff{(param - 450) % 4 + 1}"); + + if ((param - 450) % 4 + 1 != Boss3_P2_PlayerBuff[myIndex]) return; + + accessory.Log.Debug($"检测到线{tid}的Buff与主视角的Buff相同"); + + var dpl = accessory.drawRect(tid, 4f, 40f, 0, 30000, $"线{tid}"); + dpl.Color = accessory.Data.DefaultSafeColor; + dpl.Offset = new Vector3(0, -1.75f, 0); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dpl); + + // 寻找线的位置,确定剪线起始点 + if (Drawn[4]) return; + Drawn[4] = true; + + var linePos = FindLineIdx(tpos); + accessory.Log.Debug($"我在寻找{tpos}位置的线,linePos为{linePos}"); + Vector3 cutLinePos; + if (IsLineAtLeftDownSide(linePos)) + cutLinePos = CENTER_BOSS3 with { X = CENTER_BOSS3.X - 10, Z = CENTER_BOSS3.Z + 10 }; + else + cutLinePos = CENTER_BOSS3 with { X = CENTER_BOSS3.X + 10, Z = CENTER_BOSS3.Z - 10 }; + + var dp = accessory.drawStatic(cutLinePos, 0, 0, + 8000, $"剪线起始点"); + dp.Color = posColorPlayer.V4.WithW(1.5f); + dp.Scale = new Vector2(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dp0 = accessory.dirPos(cutLinePos, 0, 8000, $"剪线起始点指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + + [ScriptMethod(name: "Boss3:咒具二线条删除", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:regex:^(7738)$"], userControl: false)] + public void Boss3_P2_CutLineRemove(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P2) return; + var sid = @event.SourceId(); + accessory.Method.RemoveDraw($"线{sid}.*"); + } + + private int FindLineIdx(Vector3 pos) + { + // 1,2,3,4与11,12,13,14,>10代表竖线 + // 数小代表在左下 + var idx = 0; + if (Math.Abs(pos.X - CENTER_BOSS3.X) < 5) + { + idx += 10; + idx += (int)Math.Floor(pos.X - 283) / 2; + } + else + { + idx += (int)Math.Floor(-pos.Z - 99) / 2; + } + + return idx; + } + + private bool IsLineAtLeftDownSide(int idx) + { + return idx % 10 <= 2; + } + + [ScriptMethod(name: "Boss3:咒具三传送阵转转", eventType: EventTypeEnum.ObjectEffect, eventCondition: ["Id1:regex:^(64|256)$"])] + public void Boss3_P3_Portal(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P3) return; + var spos = @event.SourcePosition(); + var id1 = @event.Id1(); + + // 出特效的地方一定是有传送阵的地方 + Vector3 CENTER_LEFT = new(281f, 533, CENTER_BOSS3.Z); + Vector3 CENTER_RIGHT = new(297f, 533, CENTER_BOSS3.Z); + Vector3 _part_center = spos.X > CENTER_BOSS3.X ? CENTER_RIGHT : CENTER_LEFT; + + var _rot_radian = id1 switch + { + CLOCKWISE => float.Pi / 2, + COUNTER_CLOCKWISE => -float.Pi / 2, + _ => float.Pi / 2 + }; + drawRotPortal(spos, _rot_radian, _part_center, true, 0, 10000, accessory); + } + + private DrawPropertiesEdit drawRotPortal(Vector3 spos, float rot_radian, Vector3 center, bool draw, int delay, int destory, ScriptAccessory accessory) + { + // 先找到对面 + var _mirror_pos = spos.FoldPointLR(center.X); + // 再旋转 + var _tpos = _mirror_pos.RotatePoint(spos, rot_radian); + var _isSafe = isInBoss3Field(_tpos); + + var dp = accessory.drawStatic(_tpos, 0, delay, destory, $"旋转传送阵{_tpos}"); + dp.Scale = new(1.5f); + dp.Color = _isSafe ? posColorPlayer.V4.WithW(2f) : accessory.Data.DefaultDangerColor.WithW(2f); + if (draw) + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + var dp0 = accessory.drawStatic(spos, 0, delay, destory, $"旋转传送阵{spos}"); + dp0.Scale = new(1.5f); + dp0.Color = _isSafe ? posColorPlayer.V4.WithW(2f) : accessory.Data.DefaultDangerColor.WithW(2f); + if (draw) + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp0); + + if (!_isSafe) return dp; + + var row_tpos = (int)Math.Floor(_tpos.Z + 123) / 10; + var row_spos = (int)Math.Floor(spos.Z + 123) / 10; + Boss3_P3_safeTeleportPos[row_tpos] = _tpos; + Boss3_P3_safeTeleportPos[row_spos] = spos; + accessory.Log.Debug($"记录下了第{row_spos}行的安全坐标{spos}"); + accessory.Log.Debug($"记录下了第{row_tpos}行的安全坐标{_tpos}"); + return dp; + } + + private bool isInBoss3Field(Vector3 pos) + { + if (Math.Abs(pos.X - CENTER_BOSS3.X) > FIELDX_BOSS3 / 2) + return false; + if (Math.Abs(pos.Z - CENTER_BOSS3.Z) > FIELDZ_BOSS3 / 2) + return false; + return true; + } + + [ScriptMethod(name: "Boss3:咒具三玩家转转传送阵", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(2970)$"])] + public async void Boss3_P3_PlayerPortal(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P3) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + + await Task.Delay(1000); + + var param = @event.Param(); + // 逆奇,顺偶,左%5=2,右%2=1 + // 或者,数小向上传送;数大向下传送 + const uint LEFT_CW = 462; // 1 + const uint RIGHT_CW = 466; // 2 + const uint LEFT_CCW = 467; // 4 + const uint RIGHT_CCW = 461; // 3 + + Boss3_P3_myRotateRow = param switch + { + LEFT_CW => 1, + RIGHT_CW => 2, + LEFT_CCW => 4, + RIGHT_CCW => 3, + _ => 0 + }; + + accessory.Log.Debug($"我应该去第{Boss3_P3_myRotateRow}行,对应坐标{Boss3_P3_safeTeleportPos[Boss3_P3_myRotateRow-1]}"); + + drawPlayerPortal(tid, param, 14000, 3000, accessory); + var dp = accessory.dirPos(Boss3_P3_safeTeleportPos[Boss3_P3_myRotateRow-1], 0, 9000, $"玩家传送{Boss3_P3_myRotateRow}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private DrawPropertiesEdit drawPlayerPortal(uint tid, uint param, int delay, int destory, ScriptAccessory accessory) + { + var _isup = param < 464; + float _rot_radian = _isup ? DirectionCalc.BaseDirRad2InnGame(0) : DirectionCalc.BaseDirRad2InnGame(float.Pi); + + // TODO 很可惜的是,我不会在传送目的地画一个Imgui的圆形,尝试失败 + var dp = accessory.drawRect(tid, 1, 10, delay, destory, $"自传送指向{tid}"); + dp.Color = accessory.Data.DefaultSafeColor.WithW(3f); + dp.Rotation = _rot_radian; + dp.FixRotation = true; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + return dp; + } + + [ScriptMethod(name: "Boss3:咒具三引导斧头", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3278)$"])] + public void Boss3_P3_BaitAxe(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P3) return; + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + var myIndex = accessory.getMyIndex(); + if (tidx != myIndex) return; + + Vector3 baitPos; + int myBaitRow; + if (!Drawn[5]) + { + Drawn[5] = true; + myBaitRow = Boss3_P3_myRotateRow switch + { + 1 => 1, + 2 => 0, + 3 => 3, + 4 => 2, + _ => 0 + }; + baitPos = Boss3_P3_BaitPos[myBaitRow]; + } + else if (!Drawn[6]) + { + Drawn[6] = true; + myBaitRow = Boss3_P3_myRotateRow - 1; + baitPos = Boss3_P3_BaitPos[myBaitRow]; + } + else + return; + + for (int i = 0; i < 4; i++) + { + var dp0 = accessory.drawStatic(Boss3_P3_BaitPos[i], 0, 0, + 10000, $"引导斧头位置{i}"); + dp0.Color = i == myBaitRow ? posColorPlayer.V4.WithW(1.5f) : posColorNormal.V4; + dp0.Scale = new Vector2(0.5f); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp0); + } + + accessory.Log.Debug($"我应该去第{myBaitRow + 1}行引导斧头,对应坐标{baitPos}"); + var dp = accessory.dirPos(baitPos, 0, 10000, $"引导斧头位置指路{myBaitRow}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "Boss3:引导斧头指路消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(29883|30413)$"], userControl: false)] + public void Boss3_P3_BaitAxeRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw("引导斧头位置{i}.*"); + accessory.Method.RemoveDraw("引导斧头位置指路{i}.*"); + } + + // 日,写到最后发现可以通过咒具的面向看传送方向,吐了 + [ScriptMethod(name: "Boss3:咒具四立体魔法阵面向记录", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:regex:^(2013025)$", "Operate:Add"], userControl: false)] + public void Boss3_P4_BrandRecord(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P4) return; + var spos = @event.SourcePosition(); + var srot = @event.SourceRotation(); + int _idx; + if (spos.X < CENTER_BOSS3.X - 5) + _idx = 0; + else if (spos.X > CENTER_BOSS3.X + 5) + _idx = 2; + else + _idx = 1; + + Boss3_P4_BrandRot[_idx] = srot.BaseInnGame2DirRad(); + } + + [ScriptMethod(name: "Boss3:咒具四魔法阵转转", eventType: EventTypeEnum.ObjectEffect, eventCondition: ["Id1:regex:^(64|256)$"])] + public void Boss3_P4_Panels(Event @event, ScriptAccessory accessory) + { + if (phase != ASS_Phase.BOSS3_P4) return; + var spos = @event.SourcePosition(); + var id1 = @event.Id1(); + + int _idx; + if (spos.X < CENTER_BOSS3.X - 5) + _idx = 0; + else if (spos.X > CENTER_BOSS3.X + 5) + _idx = 2; + else + _idx = 1; + + var _rot_radian = id1 switch + { + CLOCKWISE => float.Pi / 2, + COUNTER_CLOCKWISE => -float.Pi / 2, + _ => float.Pi / 2 + }; + drawRotPanel(spos, _idx, _rot_radian, true, 0, 15000, accessory); + } + + private DrawPropertiesEdit drawRotPanel(Vector3 spos, int idx, float rot_radian, bool draw, int delay, int destory, ScriptAccessory accessory) + { + // 延伸方向角度 + var _rot_radian = Boss3_P4_BrandRot[idx] + rot_radian; + var _panel_pos = spos.ExtendPoint(_rot_radian, 10); + + var _panel_face_rad = idx switch + { + 0 => float.Pi / 2, + 1 => 0, + 2 => -float.Pi / 2, + _ => 0 + }; + var _sid = Boss3_P4_PanelSid[idx]; + var dp = accessory.drawStatic(_panel_pos, _panel_face_rad, delay, destory, $"圣火炮{_sid}"); + dp.Scale = new(10, 100); + dp.Color = Boss3_DefaultDangerColor ? accessory.Data.DefaultDangerColor.WithW(1f) : Boss3_SetDangerColor.V4.WithW(1f); + if (draw) accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + return 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 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 TargetId(this Event @event) + { + return ParseHexId(@event["TargetId"], out var id) ? id : 0; + } + + public static uint TargetIndex(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetIndex"]); + } + + 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static ulong DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Index(this Event @event) + { + return ParseHexId(@event["Index"], out var id) ? id : 0; + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var id) ? id : 0; + } + + public static uint DirectorId(this Event @event) + { + return ParseHexId(@event["DirectorId"], out var id) ? id : 0; + } + + public static uint DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint Id(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + public static uint Id1(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Id1"]); + } + 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 class DirectionCalc +{ + // 以北为0建立list + // InnGame List Dir + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Dir = Pi - InnGame (+ 2pi) + + /// + /// 将游戏基角度(以南为0,逆时针增加)转为逻辑基角度(以北为0,顺时针增加) + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float BaseInnGame2DirRad(this float radian) + { + float r = (float)Math.PI - radian; + if (r < 0) r = (float)(r + 2 * Math.PI); + if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float BaseDirRad2InnGame(this float radian) + { + float r = (float)Math.PI - radian; + if (r < Math.PI) r = (float)(r + 2 * Math.PI); + if (r > Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 输入逻辑基角度,获取逻辑方位 + /// + /// 逻辑基角度 + /// 方位总数 + /// 逻辑基角度对应的逻辑方位 + public static int DirRadRoundToDirs(this float radian, int dirs) + { + var r = Math.Round(radian / (2f / dirs * Math.PI)); + if (r == dirs) r = r - dirs; + return (int)r; + } + + /// + /// 输入坐标,获取正分割逻辑方位(以右上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionFloorToDirs(this Vector3 point, Vector3 center, int dirs) + { + // 正分割,0°为分界线,将360°分为dirs份 + var r = Math.Floor(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取斜分割逻辑方位(以正上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionRoundToDirs(this Vector3 point, Vector3 center, int dirs) + { + // 斜分割,0° return 0,将360°分为dirs份 + var r = Math.Round(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 将角度转为弧度 + /// + /// 角度值 + /// 对应的弧度值 + public static float angle2Rad(this float angle) + { + // 输入角度转为弧度 + float radian = (float)(angle * Math.PI / 180); + return radian; + } + + /// + /// 将弧度转为角度 + /// + /// 弧度值 + /// 对应的角度值 + public static float rad2Angle(this float radian) + { + // 输入角度转为弧度 + float angle = (float)(radian / Math.PI * 180); + return angle; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(this Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + + // TODO 另一种方案待验证 + // var nextPos = Vector3.Transform((point - center), Matrix4x4.CreateRotationY(radian)) + center; + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(this Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(this Vector3 new_point, Vector3 center) + { + // 找到某点到中心的弧度 + float radian = MathF.PI - MathF.Atan2(new_point.X - center.X, new_point.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointLR(this Vector3 point, float centerx) + { + Vector3 v3 = new(2 * centerx - point.X, point.Y, point.Z); + return v3; + } + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointUD(this Vector3 point, float centerz) + { + Vector3 v3 = new(point.X, point.Y, 2 * centerz - point.Z); + return v3; + } +} + +public static class IndexHelper +{ + /// + /// 输入玩家dataid,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int getPlayerIdIndex(this ScriptAccessory accessory, uint pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int getMyIndex(this ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataid,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string getPlayerJobByID(this ScriptAccessory accessory, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var a = accessory.Data.PartyList.IndexOf(pid); + switch (a) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// + /// + public static string getPlayerJobByIndex(this ScriptAccessory accessory, int idx) + { + switch (idx) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } +} + +public static class ColorHelper +{ + public static ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor colorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + public static ScriptColor colorDark = new ScriptColor { V4 = new Vector4(0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorLightBlue = new ScriptColor { V4 = new Vector4(0.48f, 0.40f, 0.93f, 1.0f) }; + public static ScriptColor colorWhite = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; + public static ScriptColor colorYellow = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + +} + +public static class AssignDp +{ + /// + /// 返回自己指向某目标地点的dp,可修改dp.TargetPosition, dp.Scale + /// + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos(this ScriptAccessory accessory, Vector3 target_pos, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回起始地点指向某目标地点的dp,可修改dp.Position, dp.TargetPosition, dp.Scale + /// + /// 起始地点 + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos2Pos(this ScriptAccessory accessory, Vector3 start_pos, Vector3 target_pos, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Position = start_pos; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回自己指向某目标对象的dp,可修改dp.TargetObject, dp.Scale + /// + /// 指向目标对象 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirTarget(this ScriptAccessory accessory, uint target_id, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Owner = accessory.Data.Me; + dp.TargetObject = target_id; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象目标或某定点相关的dp,可修改dp.TargetResolvePattern, dp.TargetOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawTargetOrder(this ScriptAccessory accessory, uint owner_id, uint order_idx, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.TargetOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象仇恨或距离相关的dp,可修改dp.CentreResolvePattern, dp.CentreOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCenterOrder(this ScriptAccessory accessory, uint owner_id, uint order_idx, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.CentreOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回owner与target的dp,可修改 dp.Owner, dp.TargetObject, dp.Scale + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawOwner2Target(this ScriptAccessory accessory, uint owner_id, uint target_id, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetObject = target_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCircle(this ScriptAccessory accessory, uint owner_id, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回环形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawDonut(this ScriptAccessory accessory, uint owner_id, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(22); + dp.InnerScale = new(6); + dp.Radian = float.Pi * 2; + dp.Owner = owner_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 起始位置,通常为场地中心 + /// 旋转角度,以北为0度顺时针 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawStatic(this ScriptAccessory accessory, Vector3 center, float radian, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 20); + dp.Position = center; + dp.Rotation = DirectionCalc.BaseDirRad2InnGame(radian); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回矩形 + /// + /// 起始目标id,通常为自己或Boss + /// 矩形宽度 + /// 矩形长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destory ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawRect(this ScriptAccessory accessory, uint owner_id, float width, float length, int delay, int destory, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(width, length); // 宽度为6,长度为20 + dp.Owner = owner_id; // 从哪个单位前方绘图 + dp.Color = accessory.Data.DefaultDangerColor; // 绘图颜色 + dp.Delay = delay; // 从捕获到对应日志行后,延迟多少毫秒开始绘图 + dp.DestoryAt = destory; // 从绘图出现后,经过多少毫秒绘图消失 + return dp; + } + + /// + /// 返回扇形 + /// + /// 起始目标id,通常为自己或Boss + /// 扇形弧度 + /// 扇形长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destory ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawFan(this ScriptAccessory accessory, uint owner_id, float radian, int length, int delay, int destory, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(length); + dp.Radian = radian; + dp.Owner = owner_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destory; + return dp; + } +} + +#endregion + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*$ diff --git a/lianying1997/06-EndWalker/DSR/DSR_Patch.cs b/lianying1997/06-EndWalker/DSR/DSR_Patch.cs new file mode 100644 index 0000000..3d7181e --- /dev/null +++ b/lianying1997/06-EndWalker/DSR/DSR_Patch.cs @@ -0,0 +1,3257 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Data; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Extensions; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs; +using KodakkuAssist.Module.Script.Type; + +namespace UsamisKodakku.Scripts._06_EndWalker.DSR; + +[ScriptType(name: Name, territorys: [968, 1112], guid: "cc6fb606-ff7b-4739-81aa-4861b204ab1e", + version: Version, author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*$ + +public class DsrPatch +{ + private const string NoteStr = + """ + 基于K佬绝龙诗绘图的个人向补充, + 请先按需求检查并设置“用户设置”栏目。 + 在忆罪宫输入"/e =Exaflare"可以测试地火特殊跑法。 + 鸭门。 + """; + + private const string Name = "DSR_Patch [幻想龙诗绝境战 补丁]"; + private const string Version = "0.0.0.16"; + private const string DebugVersion = "a"; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + private const bool Debugging = false; + + [UserSetting("站位提示圈绘图-普通颜色")] + public static ScriptColor PosColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public static ScriptColor PosColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) }; + + public enum ExaflareSpecStrategyEnum + { + 绝不去前方_NeverFront, + 绝不跑无脑火_NeverUniverse, + 绝不多跑_LeastMovement, + 绝对前方_AlwaysFront, + 关闭_PleaseDontDoThat, + } + [UserSetting("地火指路特殊策略")] + public static ExaflareSpecStrategyEnum ExaflareStrategy { get; set; } = ExaflareSpecStrategyEnum.绝不跑无脑火_NeverUniverse; + + [UserSetting("地火(百京核爆)使用程序预设颜色")] + public static bool ExaflareBuiltInColor { get; set; } = true; + [UserSetting("地火(百京核爆)爆炸区颜色")] + public ScriptColor ExaflareColor { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + [UserSetting("地火(百京核爆)是否绘制下一枚地火预警区")] + public static bool ExaflareWarnDrawn { get; set; } = true; + [UserSetting("地火(百京核爆)预警区颜色")] + public ScriptColor ExaflareWarnColor { get; set; } = new ScriptColor { V4 = new Vector4(0.6f, 0.6f, 1.0f, 1.0f) }; + + private enum DsrPhase + { + Init, // 初始 + Phase2Strength, // P2 一运 + Phase2Sancity, // P2 二运 + Phase3Nidhogg, // P3 大师兄 + Phase4Eyes, // P4 龙眼 + Phase5HeavensWrath, // P5 一运 + Phase5HeavensDeath, // P5 二运 + Phase6IceAndFire1, // P6 一冰火 + Phase6NearOrFar1, // P6 一远近 + Phase6Flame, // P6 十字火 + Phase6NearOrFar2, // P6 二远近 + Phase6IceAndFire2, // P6 二冰火 + Phase6Cauterize, // P6 俯冲 + Phase7Exaflare1, // P7 一地火 + Phase7Stack1, // P7 一分摊 + Phase7Nuclear1, // P7 一核爆 + Phase7Exaflare2, // P7 二地火 + Phase7Stack2, // P7 二分摊 + Phase7Nuclear2, // P7 二核爆 + Phase7Exaflare3, // P7 三地火 + Phase7Stack3, // P7 三分摊 + Phase7Enrage, // P7 狂暴 + } + + private static List _role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + private static Vector3 _center = new Vector3(100, 0, 100); + private DsrPhase _dsrPhase = DsrPhase.Init; + private List _drawn = new bool[20].ToList(); // 绘图记录 + private volatile List _recorded = new bool[20].ToList(); // 被记录flag + private int _pureOfHeartBaitCount = 0; // P1/P4.5 纯洁心灵引导次数 + private List _p2SafeDirection = new bool[8].ToList(); // P2 一运冲锋安全位置 + private Vector3 _p2ThordanPos = new Vector3(0, 0, 0); // P2 一运托尔丹位置 + private List _p2TetherKnightId = [0, 0]; // P2 一运接线骑士ID,顺序左、右 + private bool _p3DfgEnable = false; // P3 指路使能 + private static PriorityDict _dfg = new PriorityDict(); // P3 机制记录 + private List _p3TowerAppearPos = []; // P3 塔生成位置 + private int _p4MirageDiveNum = 0; // P4 幻象冲次数 + private bool _p4PrepareToCenter = false; // P4 幻象冲准备回中 + private List _p4MirageDiveNumFirstRoundTarget = new bool[8].ToList(); // P4 幻象冲第一轮目标 + private List _p4MirageDivePos = []; // P4 幻象冲目标方位,左上为0顺时针增加 + private Vector3 _p5VedrfolnirPos = new Vector3(0, 0, 0); // P5 白龙位置 + private List _p6DragonsGlowAction = [false, false]; // P6 双龙吐息记录 + private List _p6DragonsWingAction = [false, false, false]; // P6 双龙远近记录 [远T/近F,左安全T/右安全F,前安全T/后安全F/内安全T/外安全F] + private List _p7FirstEnmityOrder = [false, false]; // P7 平A仇恨记录 + private readonly List _p7TrinityOrderIdx = [4, 5, 6, 7, 2, 3]; // P7 接刀顺序 + private bool _p7TrinityDisordered = false; // P7 接刀顺序是否出错 + private bool _p7TrinityTankDisordered = false; // P7 坦克接刀仇恨是否出错 + private int _p7TrinityNum = 0; // P7 接刀次数 + private DsrExaflare? _p7Exaflare = null; // P7 地火Class + private uint _p7BossId = 0; // P7 boss Id + + private ManualResetEvent _thrustEvent = new(false); + private ManualResetEvent _thordanCastAtEdgeEvent = new(false); + private ManualResetEvent _mirageDiveRound = new(false); + private ManualResetEvent _p5VedrfolnirPosRecordEvent = new(false); + private ManualResetEvent _iceAndFireEvent = new(false); + private ManualResetEvent _nearOrFarWingsEvent = new(false); + private ManualResetEvent _nearOrFarCauterizeEvent = new(false); + private ManualResetEvent _nearOrFarInOutEvent = new(false); + private ManualResetEvent _bladeEvent = new(false); + private ManualResetEvent _trinityEvent = new(false); + + private const uint ChariotBlade = 298; + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"Init {Name} v{Version}{DebugVersion} Success.\n{UpdateInfo}"); + sa.Method.MarkClear(); + sa.Method.RemoveDraw(".*"); + + _dsrPhase = DsrPhase.Init; + _drawn = new bool[20].ToList(); + _recorded = new bool[20].ToList(); + _p7BossId = 0; + _pureOfHeartBaitShown = false; + + _thordanCastAtEdgeEvent = new ManualResetEvent(false); + _thrustEvent = new ManualResetEvent(false); + _mirageDiveRound = new ManualResetEvent(false); + _p5VedrfolnirPosRecordEvent = new ManualResetEvent(false); + _iceAndFireEvent = new ManualResetEvent(false); + _nearOrFarWingsEvent = new ManualResetEvent(false); + _nearOrFarCauterizeEvent = new ManualResetEvent(false); + _nearOrFarInOutEvent = new ManualResetEvent(false); + _bladeEvent = new ManualResetEvent(false); + _trinityEvent = new ManualResetEvent(false); + } + + #region P1 + + [ScriptMethod(name: "---- 《P1&P4.5:门神》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_PhaseDoorBoss(Event @event, ScriptAccessory accessory) + { + } + + private bool _pureOfHeartBaitShown = false; + [ScriptMethod(name: "纯洁心灵引导", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25316"], + userControl: true)] + public void PureOfHeartBait(Event @event, ScriptAccessory accessory) + { + _pureOfHeartBaitCount = 0; + _pureOfHeartBaitShown = true; + // 纯洁心灵引导顺序H1H2, D3D4,D1D2,MTST + var myIndex = accessory.GetMyIndex(); + // 此处为第一次纯洁心灵,如果非H1H2,不参与 + if (myIndex is not (2 or 3)) return; + // todo 修改delay与destroy + DrawPureOfHeartBait(accessory, 0, 15000); + } + + private void DrawPureOfHeartBait(ScriptAccessory sa, int delay, int destroy) + { + var myIndex = sa.GetMyIndex(); + Vector3[] baitPos = [new(86.5f, 0.0f, 107.0f), new(86.5f, 0.0f, 103.0f), new(91.5f, 0.0f, 107.0f), new(91.5f, 0.0f, 103.0f)]; //91.5 + var baitPosIdx = myIndex % 2; // 高在上,低在下 + if (myIndex is 0 or 1 or 6 or 7) + baitPosIdx += 2; + for (var posIdx = 0; posIdx < 4; posIdx++) + { + var color = baitPosIdx == posIdx ? PosColorPlayer.V4 : PosColorNormal.V4; + var dp = sa.DrawStaticCircle(baitPos[posIdx], color, delay, destroy, $"纯洁心灵", 0.5f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + if (baitPosIdx != posIdx) continue; + var dpGuide = sa.DrawGuidance(baitPos[posIdx], delay, destroy, $"纯洁心灵指路"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dpGuide); + } + } + + [ScriptMethod(name: "纯洁心灵引导后续", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:25369"], + userControl: false)] + public void PureOfHeartBaitRest(Event @event, ScriptAccessory sa) + { + if (!_pureOfHeartBaitShown) return; + if (@event.TargetIndex() != 1) return; + var myIndex = sa.GetMyIndex(); + lock (this) + { + _pureOfHeartBaitCount++; + sa.Log.Debug($"纯洁心灵引导次数:{_pureOfHeartBaitCount}"); + if (_pureOfHeartBaitCount > 6) return; + var baitDict = new Dictionary { { 1, 6 }, { 2, 7 }, { 3, 4 }, { 4, 5 }, { 5, 0 }, { 6, 1 } }; + if (baitDict[_pureOfHeartBaitCount] != myIndex) return; + sa.Log.Debug($"开始绘制玩家的纯洁心灵引导"); + DrawPureOfHeartBait(sa, 0, 5000); + } + } + + #endregion P1 + + #region P2 + + [ScriptMethod(name: "---- 《P2:骑神托尔丹》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_PhaseKingThordan(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "引导不可视刀范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25545"])] + public void P2_AscalonConcealed(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var dp = accessory.DrawFan(sid, float.Pi / 6, 0, 30, 0, 0, 1500, $"不可视刀"); + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "一运阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25555"], userControl: false)] + public void P2_StrengthPhaseRecord(Event @event, ScriptAccessory sa) + { + _dsrPhase = DsrPhase.Phase2Strength; + _p2SafeDirection = new bool[8].ToList(); + _p2ThordanPos = new Vector3(0, 0, 0); + _p2TetherKnightId = [0, 0]; + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "一运冲锋位置记录", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(378[123])$"], userControl: false)] + public void ThurstDirectionRecord(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase2Strength) return; + + var spos = @event.SourcePosition(); + var dir = spos.Position2Dirs(_center, 8); + lock (_p2SafeDirection) + { + _p2SafeDirection[dir % 4] = true; + sa.Log.Debug($"List内部true的数量:{_p2SafeDirection.Count(x => x)}"); + if (_p2SafeDirection.Count(x => x) != 3) return; + _thrustEvent.Set(); + } + } + + [ScriptMethod(name: "一运分散安全位置指引", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:3781"], userControl: true)] + public void ThrustSafePosDraw(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase2Strength) return; + _thrustEvent.WaitOne(); + + // 由于在处理_p2SafeDirection时,设置了其方位余4,即一定为0、1、2、3,必能在前4个index中找到唯一的false。 + var safeDir = _p2SafeDirection.IndexOf(false); + var northPos = new Vector3(100, 0, 80); + var myIndex = accessory.GetMyIndex(); + var isStGroup = myIndex % 2 == 1; + // ST组在0、1、2、3 + var tposCenter = + northPos.RotatePoint(_center, isStGroup ? safeDir * float.Pi / 4 : (safeDir + 4) * float.Pi / 4); + var tposIn = tposCenter.PointInOutside(_center, 7.5f); + var tposLeft = tposCenter.RotatePoint(_center, 20f.DegToRad()); + var tposRight = tposCenter.RotatePoint(_center, -20f.DegToRad()); + List tposList = [tposCenter, tposIn, tposLeft, tposRight]; + + var dp = accessory.DrawGuidance(tposList[myIndex / 2], 0, 7000, $"P2一运安全区位置{myIndex}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + _thrustEvent.Reset(); + } + + [ScriptMethod(name: "一运分散安全位置指引消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:25548"], userControl: false)] + public void ThrustSafePosRemove(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase2Strength) return; + var myIndex = accessory.GetMyIndex(); + + accessory.Method.RemoveDraw($"P2一运安全区位置{myIndex}"); + } + + [ScriptMethod(name: "一运托尔丹边缘位置记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25550"], userControl: false)] + public void ThordanPosRecord(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase2Strength) return; + var spos = @event.SourcePosition(); + _p2ThordanPos = spos; + _thordanCastAtEdgeEvent.Set(); + } + + [ScriptMethod(name: "一运坦克接线提示", eventType: EventTypeEnum.NpcYell, eventCondition: ["Id:regex:^(255[01])$"], userControl: true)] + public void TankTetherRouteGuidance(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase2Strength) return; + var myIndex = sa.GetMyIndex(); + if (myIndex > 1) return; + _thordanCastAtEdgeEvent.WaitOne(); + lock (_p2TetherKnightId) + { + var sid = @event.SourceId(); + var sname = @event.SourceName(); + var spos = @event.SourcePosition(); + // var rad = spos.FindRadian(_p2ThordanPos); + + var atRight = spos.IsAtRight(_p2ThordanPos, _center); + _p2TetherKnightId[atRight ? 1 : 0] = sid; + + // 此处Id为16进制转10进制表示 + sa.Log.Debug($"记录{sname}(对话{@event.Id()})在{(atRight ? "右" : "左")}"); + + if (_p2TetherKnightId.Contains(0)) return; + var targetKnightIdx = myIndex == 0 ? 0 : 1; + var chara = sa.GetById(_p2TetherKnightId[targetKnightIdx]); + if (chara == null) return; + + var knightPos = chara.Position; + var tetherEdgePos = _p2ThordanPos.RotatePoint(_center, (myIndex == 0 ? 1 : -1) * 18f.DegToRad()); + tetherEdgePos = tetherEdgePos.PointInOutside(_center, 3f); + var dp = sa.DrawGuidance(knightPos, tetherEdgePos, 0, 10000, $"接线路径"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + + [ScriptMethod(name: "一运接线提示删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:25550"], userControl: false)] + public void TankTetherRouteGuidanceRemove(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase2Strength) return; + sa.Method.RemoveDraw($"接线路径"); + _thordanCastAtEdgeEvent.Reset(); + } + + [ScriptMethod(name: "二运阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25569"], userControl: false)] + public void P2_SancityPhaseRecord(Event @event, ScriptAccessory sa) + { + _dsrPhase = DsrPhase.Phase2Sancity; + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + #endregion P2 + + #region P3 + + [ScriptMethod(name: "---- 《P3:尼德霍格》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_PhaseNidhogg(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "P3:阶段记录", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:26376"], userControl: Debugging)] + public void P3_PhaseRecord(Event ev, ScriptAccessory sa) + { + _dsrPhase = DsrPhase.Phase3Nidhogg; + _p3DfgEnable = false; + // 百位:一麻+0,二麻+100,三麻+100 + // 十位:下箭头+0,中+10,下箭头+20 + // 个位:左中右站位分别+0, +1, +2 + // 如此安排,个位可随时变,十位改变后,个位无力干涉 + _dfg.Init(sa, "堕天龙炎冲"); + _p3TowerAppearPos = []; + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "堕天龙炎冲流程指路", eventType: EventTypeEnum.StatusAdd, + eventCondition:["StatusID:regex:^(300[456])$"], userControl: true)] + public void P3_LimitCutRecord(Event ev, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase3Nidhogg) return; + _p3DfgEnable = true; + var stid = ev.StatusId; + var tid = ev.TargetId; + var tidx = sa.GetPlayerIdIndex(tid); + + var lmVal = stid switch + { + 3004 => 0, // 一麻 + 3005 => 100, // 二麻 + 3006 => 200, // 三麻 + _ => 0 + }; + lock (_dfg) + { + // 前三位一麻,中二位二麻,后三位三麻 + _dfg.AddPriority(tidx, lmVal); + sa.Log.Debug($"玩家 {sa.GetPlayerJobByIndex(tidx)} 为 {lmVal/100+1} 麻。"); + } + } + + [ScriptMethod(name: "箭头记录", eventType: EventTypeEnum.StatusAdd, + eventCondition:["StatusID:regex:^(275[567])$"], userControl: Debugging)] + public void P3_LimitCutPosRecord(Event ev, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase3Nidhogg) return; + if (!_p3DfgEnable) return; + lock (_dfg) + { + var stid = ev.StatusId; + var tid = ev.TargetId; + var tidx = sa.GetPlayerIdIndex(tid); + + var dirVal = stid switch + { + 2756 => 20, // 上箭头,上B + 2757 => 0, // 下箭头,下D + 2755 => 10, // 原地,中 + _ => 10 + }; + + _dfg.AddPriority(tidx, dirVal); + _dfg.AddActionCount(); + sa.Log.Debug($"玩家 {sa.GetPlayerJobByIndex(tidx)} 为 {dirVal switch + { + 0 => "下箭头", + 10 => "原地", + _ => "上箭头" + }}。"); + + if (_dfg.ActionCount != 8) return; + + // 获得自身数值,并依据方位更新 + var myPriority = _dfg.Priorities[sa.GetMyIndex()]; + RefreshGroupPosPriority(sa, myPriority); + sa.Log.Debug($"玩家在 {_dfg.Annotation} 机制的数值为:{myPriority}"); + } + } + + private void RefreshGroupPosPriority(ScriptAccessory sa, int myPriority) + { + // 获得同组玩家Id + var myGroupVal = (myPriority / 100) switch + { + // 此处取值含义为 + // 十位:从第几个开始取 + // 个位:取几个玩家 + 0 => 3, + 1 => 32, + 2 => 53, + _ => 0 + }; + + if (myGroupVal == 0) + { + sa.Log.Error($"GetDfgGroupPlayers 中 myGroupVal == 0"); + return; + } + + var myGroupDict = _dfg.SelectMiddlePriorityIndices(myGroupVal / 10, myGroupVal % 10); + List> myGroupPlayerIds = []; + for (int i = 0; i < myGroupVal % 10; i++) + { + var pidx = myGroupDict[i].Key; + var eid = sa.Data.PartyList[pidx]; + var prior = myGroupDict[i].Value; + myGroupPlayerIds.Add(new KeyValuePair(pidx, eid)); + sa.Log.Debug($"与我同组的玩家有{sa.GetPlayerJobByIndex(pidx)},其优先级数值为{prior}, EntityId为{eid}"); + } + + // 根据同组左右位置排序 + var sortedGroupPlayerIds = myGroupPlayerIds + .OrderBy(v => sa.GetById(v.Value).Position.X) + .ToList(); + + // 根据排序为优先级字典添加值 + for (int i = 0; i < sortedGroupPlayerIds.Count; i++) + { + var pidx = sortedGroupPlayerIds[i].Key; + // 删除个位 + _dfg.Priorities[pidx] = _dfg.Priorities[pidx] / 10 * 10; + _dfg.AddPriority(pidx, i); + + sa.Log.Debug($"检测到{sa.GetPlayerJobByIndex(pidx)}在{GetDfgPosStr(i, sortedGroupPlayerIds.Count == 2)},更新其优先级值为{_dfg.Priorities[pidx]}"); + } + } + + private string GetDfgPosStr(int myDfgIdx, bool isSecondRound = false) + { + var str = myDfgIdx switch + { + 0 => "左", + 1 => "中", + 2 => "右", + 3 => "左", + 4 => "右", + 5 => "左", + 6 => "中", + 7 => "右", + _ => "未知" + }; + + if (isSecondRound && myDfgIdx is 0 or 1) + str = myDfgIdx == 1 ? "右" : "左"; + return str; + } + + private Vector3 GetDfgTowerPosV3(int myDfgIdx) + { + var towerPos = myDfgIdx switch + { + 0 => new Vector3(_center.X - 7.5f, 0, _center.Z), + 1 => new Vector3(_center.X, 0, _center.Z + 7.5f), + 2 => new Vector3(_center.X + 7.5f, 0, _center.Z), + 3 => new Vector3(91.75f, 0, 90.8f), + 4 => new Vector3(108.25f, 0, 90.8f), + 5 => new Vector3(_center.X - 7.5f, 0, _center.Z), + 6 => new Vector3(_center.X, 0, _center.Z + 7.5f), + 7 => new Vector3(_center.X + 7.5f, 0, _center.Z), + _ => new Vector3(0, 0, 0) + }; + return towerPos; + } + + [ScriptMethod(name: "麻将流程,放塔与分摊", eventType: EventTypeEnum.StartCasting, + eventCondition:["ActionId:regex:^(2638[67])$"], userControl: Debugging)] + public void P3_LimitCutAction(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase3Nidhogg) return; + if (!_p3DfgEnable) return; + _dfg.AddActionCount(10); + // 仅需获得排序,便可知麻将流程 + var myPriority = _dfg.Priorities[sa.GetMyIndex()]; + var myDfgIdx = _dfg.FindPriorityIndexOfKey(sa.GetMyIndex()); + var hasArrow = myPriority / 10 % 10 != 1; + var posStr = GetDfgPosStr(myDfgIdx, myDfgIdx is 3 or 4); + var towerPos = GetDfgTowerPosV3(myDfgIdx); + + const int lashGnashCastTime = 7600; + const int inOutCastFirst = 3700; + const int inOutCastSecond = 3100; + const int towerExistTime = 6800; + + if (_dfg.ActionCount == 18) // 正常情况下,第一轮钢铁月环读条时,该值为18。期间五次放塔点名,第二轮钢铁月环读条时,该值为33。 + { + switch (myDfgIdx) + { + case 0: + case 1: + case 2: + sa.Log.Debug($"一麻{posStr} 第一轮,先去{posStr}{towerPos}放塔,再回人群"); + DrawTowerDir(towerPos, 0, lashGnashCastTime, $"放塔1", sa); + // 十位数代表箭头,若为1则是原地,无需画面向 + DrawTowerPosDir(towerPos, 0, lashGnashCastTime, $"放塔1面向", sa, hasArrow); + DrawBackToGroup(lashGnashCastTime, towerExistTime, $"人群", sa); + break; + case 3: + case 4: + sa.Log.Debug($"二麻{posStr} 第一轮,先回人群,再去{posStr}{towerPos}放塔"); + DrawBackToGroup(0, lashGnashCastTime, $"人群", sa); + const int jump2DelayTime = lashGnashCastTime + inOutCastFirst + inOutCastSecond; + const int jump2Destroy = 17700 - jump2DelayTime; // 17700 从下方时间节点处取 + DrawTowerDir(towerPos, jump2DelayTime, jump2Destroy, $"放塔2", sa); + DrawTowerPosDir(towerPos, jump2DelayTime, jump2Destroy, $"放塔2面向", sa, hasArrow); + break; + case 5: + case 6: + case 7: + sa.Log.Debug($"三麻{posStr} 第一轮,回人群"); + DrawBackToGroup(0, lashGnashCastTime, $"人群", sa); + break; + } + } + else if (_dfg.ActionCount == 33) + { + switch (myDfgIdx) + { + case 0: + case 2: + sa.Log.Debug($"一麻{posStr} 第二轮,引导后回人群"); + DrawBackToGroup(26900 - 21500, 28900 - 26900, $"分摊", sa); + break; + case 1: + sa.Log.Debug($"一麻{posStr} 第二轮,回人群"); + DrawBackToGroup(0, lashGnashCastTime, $"分摊", sa); + break; + case 3: + case 4: + sa.Log.Debug($"二麻{posStr} 第二轮,回人群"); + DrawBackToGroup(0, lashGnashCastTime, $"分摊", sa); + break; + case 5: + case 6: + case 7: + sa.Log.Debug($"三麻{posStr}第二轮,先去{posStr}{towerPos}放塔,再回人群"); + DrawTowerDir(towerPos, 0, lashGnashCastTime, $"放塔", sa); + DrawTowerPosDir(towerPos, 0, lashGnashCastTime, $"放塔3面向", sa, hasArrow); + DrawBackToGroup(lashGnashCastTime, towerExistTime, $"人群", sa); + break; + } + } + else + { + sa.Log.Error($"P3_LimitCutAction 出错,_dfg.ActionCount = {_dfg.ActionCount}"); + } + } + + [ScriptMethod(name: "麻将流程,踩塔指路", eventType: EventTypeEnum.ActionEffect, + eventCondition:["ActionId:regex:^(2638[234])$", "TargetIndex:1"], userControl: Debugging)] + public void P3_TowerAfterPlaced(Event ev, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase3Nidhogg) return; + // 此举动为放塔,若玩家组不按预站位处理,此时有机会对脚本进行调整 + if (!_p3DfgEnable) return; + lock (_dfg) + { + _dfg.AddActionCount(); + var tid = ev.TargetId; + var aid = ev.ActionId; + var sid = ev.SourceId; + var myDfgIdx = _dfg.FindPriorityIndexOfKey(sa.GetMyIndex()); + // 后面生成塔位置的sid已经不是原来的sid了,需要在这里找到他经偏置后的位置 + var tpos = GetTowerAppearPos(sa, sid, aid); + _p3TowerAppearPos.Add(tpos); + + var towerRound = _dfg.ActionCount switch + { + 21 => 0, + 23 => 1, + 36 => 2, + _ => -1 + }; + if (towerRound == -1) + { + sa.Log.Debug($"_dfg.ActionCount == {_dfg.ActionCount},未到数值,退出"); + return; + } + + var myPriority = _dfg.Priorities[sa.GetMyIndex()]; + // 一/二/三麻玩家放完塔,刷新组内成员相对位置,以便更改后续逻辑 + if (towerRound == myPriority / 100) + RefreshGroupPosPriority(sa, myPriority); + + // 根据三枚塔坐标左中右排序 + _p3TowerAppearPos.Sort((pos1, pos2) => pos1.X.CompareTo(pos2.X)); + + // 输入当前的轮次,以及我的优先级位次,画塔 + DrawTowerRange(sa, towerRound, myDfgIdx, myPriority); + + // 清空塔 + _p3TowerAppearPos = []; + } + } + + private DrawPropertiesEdit DrawTowerDir(Vector3 towerPos, int delay, int destroy, string name, ScriptAccessory accessory, bool draw = true) + { + var dp = accessory.DrawDirPos(towerPos, delay, destroy, name); + if (draw) + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + return dp; + } + private DrawPropertiesEdit DrawTowerPosDir(Vector3 towerPos, int delay, int destroy, string name, ScriptAccessory accessory, bool draw = true) + { + const int left = 0; + const int middle = 1; + const int right = 2; + + var targetPos = towerPos.ExtendPoint(-90f.DegToRad(), 3.1f); + var dp = accessory.DrawDirPos2Pos(towerPos, targetPos, delay, destroy, name); + dp.Scale = new Vector2(3f); + dp.Color = ColorHelper.ColorYellow.V4; + if (draw) + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + return dp; + } + + private DrawPropertiesEdit DrawBackToGroup(int delay, int destroy, string name, ScriptAccessory accessory, bool draw = true) + { + var stackPos = new Vector3(100, 0, 92); + var dp = accessory.DrawDirPos(stackPos, delay, destroy, name); + if (draw) + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + return dp; + } + + private void DrawTowerRange(ScriptAccessory sa, int towerRound, int myDfgIdx, int myPriority) + { + // 计算持续时间 + // towerExistTime - towerCastingTime + // 0, 6800 - 3000 => 3800 + // 6800 - 3000 + 300, 3000 => 3300 + // => 7100 + + const int towerExistTime = 7100; + + var myRound = myDfgIdx switch + { + // 玩家需踩第几轮塔 + 0 => 1, + 2 => 1, + 1 => 2, + 3 => 2, + 4 => 2, + 5 => 0, + 6 => 0, + 7 => 0, + _ => -1 + }; + if (myRound == -1) + { + sa.Log.Error($"myDfgIdx = {myDfgIdx} 导致 myRound = {myRound}"); + return; + } + var isMyRound = myRound == towerRound; + var myTowerPos = GetDfgPosStr(myDfgIdx); + + for (int i = 0; i < _p3TowerAppearPos.Count; i++) + { + // 当前是玩家放塔轮次,且该塔为玩家方位 + var thisTowerPos = GetDfgPosStr(i, towerRound == 1); + var isMyTower = isMyRound && (thisTowerPos == myTowerPos); + + var color = isMyTower ? sa.Data.DefaultSafeColor.WithW(1.5f) : sa.Data.DefaultDangerColor; + var dp1 = sa.DrawStaticCircle(_p3TowerAppearPos[i], color, 0, towerExistTime, $"塔{towerRound}{thisTowerPos}", 5f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + + if (!isMyTower) continue; + sa.Log.Debug($"检测到玩家需踩第 {myRound} 轮的 {myTowerPos} 塔"); + var dp01 = sa.DrawDirPos(_p3TowerAppearPos[i], 0, towerExistTime, $"塔{towerRound}{thisTowerPos}指路"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp01); + } + } + + private Vector3 GetTowerAppearPos(ScriptAccessory sa, ulong sid, uint type) + { + // const uint inPlace = 26382; + // const uint front = 26383; + // const uint behind = 26384; + + var chara = sa.GetById(sid); + var srot = chara.Rotation; + var spos = chara.Position; + + if (type == 26382) return spos; + var newPos = spos.ExtendPoint(srot.Game2Logic(), 14); + return newPos; + } + + // 0 Casting LashGnash 0 + // +7600 Stack #1 + Jump #1 7600 + // +3700 Chariot/Donut #1 11300 + // +3100 Donut/Chariot #1 14400 + // +0 Towers #1 14400 + // +2500 StartCast Geirskogul #1 16900 + // +800 Jump #2 17700 + // +3800 Casting LashGnash 21500 + // +2800 Towers #2 24300 + // +2600 StartCast Geirskogul #2 26900 + // +2200 Stack #2 + Jump #3 28900 + // +3700 Chariot/Donut #2 32600 + // +3100 Donut/Chariot #2 35700 + // +0 Towers #3 35700 + // +2000 StartCast Geirskogul #3 37700 + // +4500 Geirskogul #3 42200 + + // TowerExistTime 6800, 6600, 6800 + // PlaceTowerTimeNode 7600, 17700, 28900 + + #endregion P3 + + #region P4 + + [ScriptMethod(name: "---- 《P4:龙眼》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_PhaseEyes(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "P4阶段记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2748"], + userControl: false)] + public void P4_EyesPhaseRecord(Event @event, ScriptAccessory sa) + { + if (_dsrPhase == DsrPhase.Phase4Eyes) return; + _dsrPhase = DsrPhase.Phase4Eyes; + _p4MirageDiveNum = 0; + _p4MirageDiveNumFirstRoundTarget = new bool[8].ToList(); + _p4MirageDivePos = []; + _p4PrepareToCenter = false; + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "开场就位提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2748"], + userControl: true)] + public void EyesTargetMention(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + var myIndex = accessory.GetMyIndex(); + // MT D1 D2 H1 + var isBlueEye = myIndex is 0 or 2 or 4 or 5; + var isTank = myIndex is 0 or 1; + accessory.Method.TextInfo($"{(isTank ? "开启盾姿," : "")}{(isBlueEye ? "左侧蓝球" : "右侧红球")}就位", 3000, isTank); + } + + [ScriptMethod(name: "红蓝Buff置换提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(277[56])$"], + userControl: true)] + public void EyesBuffExchange(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + const uint redBuff = 2775; + const uint blueBuff = 2776; + var stid = @event.StatusId(); + var myIndex = accessory.GetMyIndex(); + if (_drawn[0]) return; + _drawn[0] = true; + + var needChange = (myIndex < 4 && stid != blueBuff) || (myIndex >= 4 && stid != redBuff); + if (!needChange) return; + var dp = accessory.DrawGuidance(_center, 0, 5000, $"红蓝Buff置换"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + accessory.Method.TextInfo($"场中换Buff", 3000); + } + + [ScriptMethod(name: "红蓝Buff置换消除", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(277[56])$"], + userControl: false)] + public void EyesBuffExchangeRemove(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + const uint redBuff = 2775; + const uint blueBuff = 2776; + var stid = @event.StatusId(); + var myIndex = accessory.GetMyIndex(); + + var changeComplete = (myIndex < 4 && stid == blueBuff) || (myIndex >= 4 && stid == redBuff); + if (!changeComplete) return; + accessory.Method.RemoveDraw($"红蓝Buff置换"); + } + + [ScriptMethod(name: "DPS撞球提示", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(1260[78])$"], + userControl: true)] + public void PobYellowOrbsGuidance(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + if (_drawn[1]) return; + _drawn[1] = true; + // 球出现开始计时 + var myIndex = accessory.GetMyIndex(); + if (myIndex < 4) return; + + var orbPos = new Vector3(83, 0, 100); + if (myIndex is 6 or 7) + orbPos = orbPos.FoldPointHorizon(_center.X); + + // 要细致的话,需要找到球什么时候变大的时间点 + var dp0 = accessory.DrawGuidance(orbPos, 4000, 2000, $"DPS撞球准备"); + dp0.Color = accessory.Data.DefaultDangerColor; + var dp1 = accessory.DrawGuidance(orbPos, 6000, 5000, $"DPS撞球"); + dp1.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + + [ScriptMethod(name: "DPS撞球提示消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26817"], + userControl: false)] + public void PobYellowOrbsGuidanceRemove(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + var myIndex = accessory.GetMyIndex(); + if (myIndex < 4) return; + accessory.Method.RemoveDraw($"DPS撞球.*"); + } + + [ScriptMethod(name: "TN撞球提示", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(1260[78])$"], + userControl: true)] + public void PobBlueOrbsGuidance(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + if (_drawn[2]) return; + _drawn[2] = true; + // 球出现开始计时 + var myIndex = accessory.GetMyIndex(); + if (myIndex >= 4) return; + + var orbPos = new Vector3(90, 0, 93); + if (myIndex >= 2) + orbPos = orbPos.FoldPointVertical(_center.Z); + if (myIndex % 2 == 1) + orbPos = orbPos.FoldPointHorizon(_center.X); + + // accessory.Method.TextInfo($"与DPS换Buff", 2500); + var dp0 = accessory.DrawGuidance(orbPos, 10000, 2000, $"TN撞球准备"); + dp0.Color = accessory.Data.DefaultDangerColor; + var dp1 = accessory.DrawGuidance(orbPos, 12000, 5000, $"TN撞球"); + dp1.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + + [ScriptMethod(name: "TN撞球前换Buff提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26817"], + userControl: true)] + public void BuffExchangeHintBeforePobBlueOrbs(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + if (_drawn[5]) return; + _drawn[5] = true; + // 球出现开始计时 + var myIndex = accessory.GetMyIndex(); + if (myIndex >= 4) return; + + accessory.Method.TextInfo($"与DPS换Buff", 2500); + } + + [ScriptMethod(name: "TN撞球提示消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26815"], + userControl: false)] + public void PobBlueOrbsGuidanceRemove(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + var myIndex = accessory.GetMyIndex(); + if (myIndex >= 4) return; + accessory.Method.RemoveDraw($"TN撞球.*"); + } + + [ScriptMethod(name: "幻象冲初始就位提示", eventType: EventTypeEnum.RemoveCombatant, eventCondition: ["DataId:12607"], + userControl: true)] + public void MirageDiveStandPosMention(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + if (_drawn[3]) return; + _drawn[3] = true; + + Vector3 targetPos; + var myIndex = accessory.GetMyIndex(); + if (myIndex >= 4) + targetPos = new(90, 0, 100); + else + { + targetPos = new(84.5f, 0, 94.5f); + targetPos = targetPos.RotatePoint(new(90, 0, 100), myIndex * 90f.DegToRad()); + } + var dp = accessory.DrawGuidance(targetPos, 0, 5000, $"幻象冲就位提示"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "幻象冲次数与目标记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26820", "TargetIndex:1"], + userControl: false)] + public void MirageDiveNumRecord(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + var tid = @event.TargetId(); + var tidx = accessory.GetPlayerIdIndex(tid); + lock (_p4MirageDiveNumFirstRoundTarget) + { + _p4MirageDiveNum++; + if (_p4MirageDiveNum <= 2) + _p4MirageDiveNumFirstRoundTarget[tidx] = true; + } + + lock (_p4MirageDivePos) + { + var tpos = @event.TargetPosition(); + var tdir = tpos.Position2Dirs(new Vector3(90, 0, 100), 4, false); + _p4MirageDivePos.Add((tdir + 1) % 4); + if (_p4MirageDivePos.Count != 2) return; + _p4MirageDivePos.Sort(); + _mirageDiveRound.Set(); + } + } + + [ScriptMethod(name: "幻象冲等待回中提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26820", "TargetIndex:1"], + userControl: true)] + public void MirageDiveBackToCenterMentionAwait(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + if (_p4PrepareToCenter) return; + var tid = @event.TargetId(); + if (tid != sa.Data.Me) return; + if (_p4MirageDiveNum > 6) return; + _p4PrepareToCenter = true; + + var dp = sa.DrawGuidance(new Vector3(90, 0, 100), 0, 5000, $"幻象冲等待回中提示"); + dp.Color = sa.Data.DefaultDangerColor; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + sa.Log.Debug($"玩家受到伤害,准备回中"); + } + + [ScriptMethod(name: "幻象冲回中提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2776"], + userControl: true)] + public void MirageDiveBackToCenterMention(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + if (!_p4PrepareToCenter) return; + var tid = @event.TargetId(); + if (tid != sa.Data.Me) return; + if (_p4MirageDiveNum > 6) return; + _p4PrepareToCenter = false; + + sa.Method.RemoveDraw($"幻象冲等待回中提示"); + var dp = sa.DrawGuidance(new Vector3(90, 0, 100), 0, 2500, $"幻象冲回中提示"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + sa.Log.Debug($"玩家Buff交换完毕,回中"); + } + + [ScriptMethod(name: "幻象冲交换提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26820", "TargetIndex:1"], + userControl: true)] + public void MirageDiveSwapMention(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase4Eyes) return; + if (_drawn[4]) return; + _drawn[4] = true; + _mirageDiveRound.WaitOne(); + + _drawn[4] = false; + _mirageDiveRound.Reset(); + + if (_p4MirageDiveNum > 6) return; + var highPriorityPlayer = _p4MirageDiveNum switch + { + 2 => 4, + 4 => 6, + 6 => _p4MirageDiveNumFirstRoundTarget.IndexOf(true), + _ => 0, + }; + var lowPriorityPlayer = _p4MirageDiveNum switch + { + 2 => 5, + 4 => 7, + 6 => _p4MirageDiveNumFirstRoundTarget.LastIndexOf(true), + _ => 0, + }; + + var basePos = new Vector3(84.5f, 0, 94.5f); + var highPriorityPos = basePos.RotatePoint(new(90, 0, 100), _p4MirageDivePos[0] * 90f.DegToRad()); + var lowPriorityPos = basePos.RotatePoint(new(90, 0, 100), _p4MirageDivePos[1] * 90f.DegToRad()); + + var highPriorityPlayerJob = sa.GetPlayerJobByIndex(highPriorityPlayer); + var lowPriorityPlayerJob = sa.GetPlayerJobByIndex(lowPriorityPlayer); + var myIndex = sa.GetMyIndex(); + + if (myIndex == highPriorityPlayer) + { + var dp = sa.DrawGuidance(highPriorityPos, 0, 5000, $"高优先级就位{highPriorityPlayer}"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + if (myIndex == lowPriorityPlayer) + { + var dp = sa.DrawGuidance(lowPriorityPos, 0, 5000, $"低优先级就位{lowPriorityPlayer}"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + var str = ""; + str += $"第{_p4MirageDiveNum / 2}轮,高优先级{highPriorityPlayerJob}去{_p4MirageDivePos[0]}号位\n"; + str += $"第{_p4MirageDiveNum / 2}轮,低优先级{lowPriorityPlayerJob}去{_p4MirageDivePos[1]}号位"; + sa.Log.Debug(str); + _p4MirageDivePos.Clear(); + } + + #endregion P4 + + #region P5 + + [ScriptMethod(name: "---- 《P5:伪典托尔丹》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_PhaseAlternateThordan(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "P5:一运,阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27529"], userControl: false)] + public void P5_HeavensWrath_PhaseRecord(Event @event, ScriptAccessory sa) + { + _dsrPhase = DsrPhase.Phase5HeavensWrath; + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "旋风冲旋风预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27531"])] + public async void P5_TwistingDive(Event @event, ScriptAccessory accessory) + { + DrawTwister(3000, 3000, accessory); + await Task.Delay(3000); + accessory.Method.TextInfo("旋风", 3000, true); + } + + [ScriptMethod(name: "旋风危险位置", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2001168", "Operate:Add"])] + public void TwisterField(Event @event, ScriptAccessory accessory) + { + var spos = @event.SourcePosition(); + var dp = accessory.DrawStaticCircle(spos, ColorHelper.ColorRed.V4.WithW(3), 0, 4000, $"旋风{spos}"); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void DrawTwister(int delay, int destroy, ScriptAccessory accessory) + { + for (var i = 0; i < accessory.Data.PartyList.Count; i++) + { + var dp = accessory.DrawCircle(accessory.Data.PartyList[i], 1.5f, delay, destroy, $"旋风{i}", true); + dp.Color = accessory.Data.DefaultDangerColor.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "大圈火预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:25573"])] + public void P5_AlterFlare(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase5HeavensWrath) return; + var spos = @event.SourcePosition(); + var dp = accessory.DrawStaticCircle(spos, ColorHelper.ColorRed.V4.WithW(1.5f), 0, 4000, $"大圈火危险区", 8f); + dp.ScaleMode |= ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "一运白龙位置记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27531"], + userControl: false)] + public void VedrfolnirPosRecord(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase5HeavensWrath) return; + var spos = @event.SourcePosition(); + _p5VedrfolnirPos = spos; + _p5VedrfolnirPosRecordEvent.Set(); + } + + [ScriptMethod(name: "一运连线指路", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0005"], + userControl: true)] + public void SpiralPierceTetherGuidance(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase5HeavensWrath) return; + _p5VedrfolnirPosRecordEvent.WaitOne(); + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + var spos = @event.SourcePosition(); + var atRight = spos.IsAtRight(_p5VedrfolnirPos, _center); + var targetPos = spos.RotatePoint(_center, (atRight ? 1 : -1) * 172.5f.DegToRad()); + + targetPos = targetPos.PointInOutside(_center, 2f); + var dp = accessory.DrawGuidance(targetPos, 0, 8000, $"一运连线指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "一运连线指路消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:27530"], + userControl: false)] + public void SpiralPierceTetherGuidanceRemove(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase5HeavensWrath) return; + accessory.Method.RemoveDraw($"一运连线指路"); + } + + [ScriptMethod(name: "一运穿天指路", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:000E"], + userControl: true)] + public void SkywardLeapGuidance(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase5HeavensWrath) return; + _p5VedrfolnirPosRecordEvent.WaitOne(); + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + + var targetPos = _p5VedrfolnirPos.RotatePoint(_center, -67.5f.DegToRad()); + targetPos = targetPos.PointInOutside(_center, 2f); + var dp = accessory.DrawGuidance(targetPos, 0, 8000, $"一运穿天指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "一运穿天指路消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:29346"], + userControl: false)] + public void SkywardLeapGuidanceRemove(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase5HeavensWrath) return; + _p5VedrfolnirPosRecordEvent.Reset(); + accessory.Method.RemoveDraw($"一运穿天指路"); + } + + [ScriptMethod(name: "P5:二运,阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27538"], userControl: false)] + public void P5_HeavensDeath_PhaseRecord(Event @event, ScriptAccessory sa) + { + _dsrPhase = DsrPhase.Phase5HeavensDeath; + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "二运斧头哥方位指引", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7747", "SourceDataId:12637"])] + public void P5_FindSerGuerrique(Event @event, ScriptAccessory sa) + { + if (_dsrPhase != DsrPhase.Phase5HeavensDeath) return; + var spos = @event.SourcePosition(); + sa.Log.Debug($"找到斧头哥位置{spos}"); + var dp = sa.DrawDirPos2Pos(_center, spos, 0, 4000, $"场中指向斧头哥", 2f); + dp.Color = ColorHelper.ColorWhite.V4; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + #endregion P5 + + #region P6 冰火 + + [ScriptMethod(name: "---- 《P6:双龙》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_PhaseDragons(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "P6:一冰火,阶段记录", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:12613"], userControl: false)] + public void P6_IceAndFire1_PhaseRecord(Event @event, ScriptAccessory sa) + { + // 圣龙出现代表进入一冰火 + if (_dsrPhase != DsrPhase.Phase5HeavensDeath) return; + _dsrPhase = DsrPhase.Phase6IceAndFire1; + _p6DragonsGlowAction = [false, false]; + _recorded = new bool[20].ToList(); + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "P6:二冰火,阶段记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(2794[79])$"], userControl: false)] + public void P6_IceAndFire2_PhaseRecord(Event @event, ScriptAccessory sa) + { + // 以辣翅辣尾作为二冰火的开始 + if (_dsrPhase != DsrPhase.Phase6NearOrFar2) return; + _dsrPhase = DsrPhase.Phase6IceAndFire2; + _p6DragonsGlowAction = [false, false]; + _recorded = new bool[20].ToList(); + sa.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + + [ScriptMethod(name: "P6:冰火吐息记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(2795[4567])$"], userControl: false)] + public void P6_IceAndFireGlowRecord(Event @event, ScriptAccessory accessory) + { + const uint blackBuster = 27954; + const uint whiteBuster = 27956; + const uint blackGlow = 27955; + const uint whiteGlow = 27957; + + if (_dsrPhase != DsrPhase.Phase6IceAndFire1 && _dsrPhase != DsrPhase.Phase6IceAndFire2) return; + var aid = @event.ActionId(); + switch (aid) + { + case blackBuster: + case blackGlow: + _p6DragonsGlowAction[0] = aid == blackGlow; + break; + case whiteBuster: + case whiteGlow: + _p6DragonsGlowAction[1] = aid == whiteGlow; + break; + } + + lock (_recorded) + { + _recorded[1] = _recorded[0]; + _recorded[0] = true; + if (_recorded[0] && _recorded[1]) + _iceAndFireEvent.Set(); + } + } + + [ScriptMethod(name: "冰火死刑双T处理", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27960"])] + public void P6_IceAndFireTankSolution(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase is not (DsrPhase.Phase6IceAndFire1 or DsrPhase.Phase6IceAndFire2)) + return; + _iceAndFireEvent.WaitOne(); + // await Task.Delay(100); + var myIndex = accessory.GetMyIndex(); + var tankBusterPosition = new Vector3[4]; + tankBusterPosition[0] = new Vector3(84.5f, 0, 88f); + tankBusterPosition[1] = tankBusterPosition[0].FoldPointHorizon(_center.X); + tankBusterPosition[2] = tankBusterPosition[0]; + tankBusterPosition[3] = tankBusterPosition[1].FoldPointVertical(_center.Z); + + if (_p6DragonsGlowAction[0] && _p6DragonsGlowAction[1]) + { + // 场中分摊死刑,自己不是T不显示指路 + if (myIndex > 1) return; + // 删除K佬脚本中双T的小啾啾 + accessory.Method.RemoveDraw("P6 第二次冰火线ND站位.*"); + var dp = accessory.DrawDirPos(_center, 0, 6000, $"冰火场中分摊指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + else + { + // 场边死刑,自己的死刑不显示圈,避免瞎眼 + var busterIdx = _p6DragonsGlowAction.FindIndex(x => x == false); + + var str = ""; + str += $"黑龙喷:{_p6DragonsGlowAction[0]}, 白龙喷:{_p6DragonsGlowAction[1]}\n"; + str += $"是{(busterIdx == 0 ? "黑龙" : "白龙")}的死刑。"; + accessory.Log.Debug($"{str}"); + + var isMyBuster = myIndex == busterIdx; + var dp = accessory.DrawCircle(accessory.Data.PartyList[busterIdx], isMyBuster ? 2f : 15f, 0, 6000, $"冰火死刑"); + dp.Color = isMyBuster ? ColorHelper.ColorRed.V4 : ColorHelper.ColorYellow.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + // 场边分散,自己不是T不显示指路 + if (myIndex > 1) return; + // 删除K佬脚本中双T的小啾啾 + accessory.Method.RemoveDraw("P6 第二次冰火线ND站位.*"); + var isIceAndFire2 = _dsrPhase == DsrPhase.Phase6IceAndFire2; + + var dp0 = accessory.DrawDirPos(tankBusterPosition[isIceAndFire2 ? myIndex + 2 : myIndex], 0, 6000, + $"冰火死刑位置指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + + var dp1 = accessory.DrawStaticCircle(tankBusterPosition[isIceAndFire2 ? myIndex + 2 : myIndex], + PosColorPlayer.V4.WithW(1.5f), 0, 6000, $"冰火死刑点区域", 1f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + } + _iceAndFireEvent.Reset(); + } + + #endregion P6 冰火 + + #region P6 远近 + + [ScriptMethod(name: "P6:远近,阶段记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:27970"], userControl: false)] + public void P6_NearOrFar_PhaseRecord(Event @event, ScriptAccessory accessory) + { + // 因为黑龙先飞,白龙后读条,所以用无尽轮回的ActionEffect做阶段节点 + if (_dsrPhase is DsrPhase.Phase6NearOrFar1 or DsrPhase.Phase6NearOrFar2) + return; + _dsrPhase = _dsrPhase switch + { + DsrPhase.Phase6IceAndFire1 => DsrPhase.Phase6NearOrFar1, + DsrPhase.Phase6Flame => DsrPhase.Phase6NearOrFar2, + _ => DsrPhase.Phase6NearOrFar1, + }; + _p6DragonsWingAction = [false, false, false]; // P6 双龙远近记录 + accessory.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "P6:远近,翅膀记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(279(39|4[023]))$"], userControl: false)] + public void P6_NearOrFar_WingsRecord(Event @event, ScriptAccessory accessory) + { + // LEFT左翼发光,玩家视角左侧安全。 + const uint leftFar = 27940; + const uint leftNear = 27939; + const uint rightFar = 27943; + // const uint rightNear = 27942; + + if (_dsrPhase is not (DsrPhase.Phase6NearOrFar1 or DsrPhase.Phase6NearOrFar2)) + return; + + var aid = @event.ActionId(); + // [远T/近F,左安全T/右安全F,前安全T/后安全F/内安全T/外安全F] + _p6DragonsWingAction[0] = aid is leftFar or rightFar; + _p6DragonsWingAction[1] = aid is leftFar or leftNear; + accessory.Log.Debug($"检测到{(_p6DragonsWingAction[0] ? "T远离" : "T靠近")}, {(_p6DragonsWingAction[1] ? "左" : "右")}安全"); + _nearOrFarWingsEvent.Set(); + } + + + [ScriptMethod(name: "P6:远近,俯冲记录", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7747", "SourceDataId:12612"], userControl: false)] + public void P6_NearOrFar_CauterizeRecord(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase6NearOrFar1) return; + var spos = @event.SourcePosition(); + // [远T/近F,左安全T/右安全F,前安全T/后安全F/内安全T/外安全F] + _p6DragonsWingAction[2] = spos.X < _center.X; + accessory.Log.Debug($"检测到{(_p6DragonsWingAction[2] ? "前安全" : "后安全")}"); + _nearOrFarCauterizeEvent.Set(); + } + + [ScriptMethod(name: "P6:远近,内外记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(2794[79])$"], userControl: false)] + public void P6_NearOrFar_BlackWingsRecord(Event @event, ScriptAccessory accessory) + { + const uint insideSafe = 27947; + // const uint outsideSafe = 27949; + if (_dsrPhase != DsrPhase.Phase6NearOrFar2) return; + var aid = @event.ActionId(); + // [远T/近F,左安全T/右安全F,前安全T/后安全F/内安全T/外安全F] + _p6DragonsWingAction[2] = aid == insideSafe; + accessory.Log.Debug($"检测到{(_p6DragonsWingAction[2] ? "内安全" : "外安全")}"); + _nearOrFarInOutEvent.Set(); + } + + [ScriptMethod(name: "一远近指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(279(39|4[023]))$"])] + public void P6_NearOrFar1_Dir(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase6NearOrFar1) return; + _nearOrFarCauterizeEvent.WaitOne(); + _nearOrFarWingsEvent.WaitOne(); + Vector3[] nearOrFarSafePos = GetQuarterSafePos(_p6DragonsWingAction); + var nearOrFarDirPosIdx = GetQuarterSafePosIdx(_p6DragonsWingAction); + accessory.Log.Debug($"MT去{nearOrFarDirPosIdx[0]}, ST去{nearOrFarDirPosIdx[1]}, 人群去{nearOrFarDirPosIdx[2]}"); + + var myIndex = accessory.GetMyIndex(); + var myPartIdx = myIndex >= 2 ? 2 : myIndex; + var targetPos = nearOrFarSafePos[nearOrFarDirPosIdx[myPartIdx]]; + + for (var i = 0; i < 3; i++) + { + var tempPos = nearOrFarSafePos[nearOrFarDirPosIdx[i]]; + var color = i == myPartIdx ? PosColorPlayer.V4.WithW(1.5f) : PosColorNormal.V4; + var dp0 = accessory.DrawStaticCircle(tempPos, color, 0, 7500, $"一远近位置{i}", 1f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + } + + var dp = accessory.DrawDirPos(targetPos, 0, 7500, $"一远近指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + _nearOrFarCauterizeEvent.Reset(); + _nearOrFarWingsEvent.Reset(); + } + + private Vector3[] GetQuarterSafePos(List wings) + { + // 第一象限内的四个端点 + // 象限内四个点Idx顺序为,以第一象限基准(面向白龙左上),从左上开始顺时针 + // 上下平移,左右折叠 + Vector3[] quarterSafePos = new Vector3[4]; + quarterSafePos[0] = new Vector3(120f, 0, 80f); + quarterSafePos[1] = new Vector3(120f, 0, 98f); + quarterSafePos[2] = new Vector3(102f, 0, 98f); + quarterSafePos[3] = new Vector3(102f, 0, 80f); + for (var i = 0; i < 4; i++) + { + // 后安全,向后平移 + if (!wings[2]) + quarterSafePos[i] -= new Vector3(22f, 0, 0); + // 右安全,左右折叠 + if (!wings[1]) + quarterSafePos[i] = quarterSafePos[i].FoldPointVertical(_center.Z); + } + return quarterSafePos; + } + + private static int[] GetQuarterSafePosIdx(List wings) + { + // return数组,代表MT、ST、人群的安全位置Index + + // 打远,双T远离,人群靠近 + // 打近,双T靠近,人群远离 + return wings[0] ? [2, 3, 1] : [1, 0, 3]; + } + + [ScriptMethod(name: "二远近指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(2794[79])$"])] + public void P6_NearOrFar2_Dir(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase6NearOrFar2) return; + _nearOrFarInOutEvent.WaitOne(); + _nearOrFarWingsEvent.WaitOne(); + + Vector3[] nearOrFarSafePos = GetLineSafePos(_p6DragonsWingAction); + int[] nearOrFarDirPosIdx = GetLineSafePosIdx(_p6DragonsWingAction); + + var myIndex = accessory.GetMyIndex(); + var myPartIdx = myIndex >= 2 ? 2 : myIndex; + var targetPos = nearOrFarSafePos[nearOrFarDirPosIdx[myPartIdx]]; + + for (var i = 0; i < 3; i++) + { + var color = i == myPartIdx ? PosColorPlayer.V4.WithW(1.5f) : PosColorNormal.V4; + var tempPos = nearOrFarSafePos[nearOrFarDirPosIdx[i]]; + var dp0 = accessory.DrawStaticCircle(tempPos, color, 0, 7500, $"二远近位置{i}", 1f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + } + + var dp = accessory.DrawDirPos(targetPos, 0, 7500, $"二远近指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + _nearOrFarInOutEvent.Reset(); + _nearOrFarWingsEvent.Reset(); + } + + private static Vector3[] GetLineSafePos(List wings) + { + // 直线近中远三点 + Vector3[] lineSafePos = new Vector3[3]; + lineSafePos[0] = new Vector3(120f, 0, 100f); + lineSafePos[1] = new Vector3(100f, 0, 100f); + lineSafePos[2] = new Vector3(80f, 0, 100f); + + Vector3 dv3 = new(0f, 0f, 0f); + + // 左安全减,右安全加 + dv3 += new Vector3(0f, 0f, 2f) * (wings[1] ? -1 : 1); + // 内安全不动,外安全乘 + dv3 *= wings[2] ? 1 : 5; + + for (var i = 0; i < 3; i++) + lineSafePos[i] += dv3; + + return lineSafePos; + } + + private static int[] GetLineSafePosIdx(List wings) + { + // return数组,代表MT、ST、人群的安全位置Index + + // 打远,双T远离,人群靠近 + // 打近,双T靠近,人群远离 + return wings[0] ? [1, 2, 0] : [1, 0, 2]; + } + + #endregion P6 远近 + + #region P6 十字火 + + [ScriptMethod(name: "P6:十字火,阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27973"], userControl: false)] + public void P6_Flame_PhaseRecord(Event @event, ScriptAccessory accessory) + { + _dsrPhase = DsrPhase.Phase6Flame; + accessory.Log.Debug($"当前阶段为:{_dsrPhase}"); + } + + [ScriptMethod(name: "十字火分摊目标", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27974"])] + public void P6_FlameStackTarget(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase6Flame) return; + var tid = @event.TargetId(); + var dp = accessory.DrawCircle(tid, 6, 0, 12500, $"死亡轮回目标"); + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion P6 十字火 + + #region P6 俯冲 + + [ScriptMethod(name: "俯冲双T指路", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7737", "SourceDataId:12613"])] + public void P6_CauterizeDir(Event @event, ScriptAccessory accessory) + { + if (_dsrPhase != DsrPhase.Phase6IceAndFire2) return; + _dsrPhase = DsrPhase.Phase6Cauterize; + accessory.Log.Debug($"当前阶段为:{_dsrPhase}"); + + Vector3[] cauterizePos = new Vector3[2]; + cauterizePos[0] = new Vector3(95f, 0, 79f); + cauterizePos[1] = new Vector3(105f, 0, 79f); + + var myIndex = accessory.GetMyIndex(); + if (myIndex > 1) return; + + var dp = accessory.DrawDirPos(cauterizePos[myIndex], 0, 5000, $"俯冲T挡枪位置{myIndex}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + #endregion P6 俯冲 + + #region P7 地火 + + [ScriptMethod(name: "---- 《P7:龙威骑神托尔丹》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_PhaseDragonKingThordan(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "P7:BossId记录与地火类初始化", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["SourceDataId:12616"], userControl: false)] + public void P7_BossIdRecord(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + _p7BossId = sid; + List scoreList = ExaflareStrategy switch + { + // moveStep,isFront,isUniverse + ExaflareSpecStrategyEnum.绝不去前方_NeverFront => [2, 100, 50], + ExaflareSpecStrategyEnum.绝不跑无脑火_NeverUniverse => [2, 10, 100], + ExaflareSpecStrategyEnum.绝不多跑_LeastMovement => [20, 10, 50], + ExaflareSpecStrategyEnum.绝对前方_AlwaysFront => [2, -10, 50], + _ => [-10, 100, 0], + }; + _p7Exaflare = new DsrExaflare(scoreList); + } + + + [ScriptMethod(name: "P7:钢铁月环剑记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2056", "Param:regex:^(29[89])$"], userControl: false)] + public void P7_BossBladeRecord(Event @event, ScriptAccessory accessory) + { + var param = @event.Param(); + accessory.Log.Debug($"钢铁月环剑:{param}(298钢铁,299月环)"); + _p7Exaflare?.SetBladeType(param); + if (!IsExaflarePhase()) return; + _bladeEvent.Set(); + } + + [ScriptMethod(name: "地火范围绘制", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28060"])] + public void P7_ExaflareDrawn(Event @event, ScriptAccessory accessory) + { + // 面相为前、左、右的扩散 + var spos = @event.SourcePosition(); + var srot = @event.SourceRotation(); + var bossChara = accessory.GetById(_p7BossId); + var bossRot = bossChara?.Rotation ?? float.Pi; + var bossPos = bossChara?.Position ?? _center; + const int intervalTime = 1900; + const int castTime = 6900; + const int extendDistance = 7; + const int dirNum = 3; + const int extNum = 6; + const int advWarnNum = 1; // 预警向外延伸几个 + float[] flareRot = [0, -float.Pi / 2, float.Pi / 2]; + + Vector3[,] exaflarePos = BuildExaflareVector(spos, dirNum, extNum, srot, flareRot, extendDistance); + DrawExaflareScene(exaflarePos, ExaflareWarnDrawn, advWarnNum, castTime, intervalTime, accessory); + + if (_p7Exaflare == null) return; + lock (_p7Exaflare) + { + _p7Exaflare.SetBossPos(bossPos, accessory); + _p7Exaflare.AddExaflare(spos, bossRot, srot, accessory); + } + } + + [ScriptMethod(name: "地火特殊解法指路", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2056", "Param:regex:^(29[89])$"])] + public void P7_ExaflareGuidance(Event @event, ScriptAccessory accessory) + { + // 记录完钢铁月环后可计算 + if (_p7Exaflare == null) return; + if (!IsExaflarePhase()) return; + if (ExaflareStrategy == ExaflareSpecStrategyEnum.关闭_PleaseDontDoThat) return; + if (!_p7Exaflare.ExaflareRecordComplete()) return; + _bladeEvent.WaitOne(); + var guidePosList = _p7Exaflare.ExportExaflareSolution(accessory); + accessory.Log.Debug($"你选择的策略是{ExaflareStrategy}"); + DrawExaflareGuidePos(guidePosList, accessory); + _bladeEvent.Reset(); + } + + private void DrawExaflareGuidePos(List guidePosList, ScriptAccessory accessory) + { + const int intervalTime = 1900; + const int castTime = 6900; + const int baseTime = castTime - 900; // 900ms为冰火剑附加到托尔丹身上的时间 + + for (var i = 0; i < guidePosList.Count; i++) + { + var delay = i == 0 ? 0 : baseTime + (i - 1) * intervalTime; + var destroy = i == 0 ? baseTime : intervalTime; + + var dp01 = accessory.DrawDirPos(guidePosList[i], delay, destroy, $"地火第{i}步-玩家-位置"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp01); + if (i >= guidePosList.Count - 1) continue; + var dp12 = accessory.DrawDirPos2Pos(guidePosList[i], guidePosList[i + 1], delay, destroy, $"地火第{i}步-位置-位置"); + dp12.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp12); + } + } + + /// + /// 画地火场景 + /// + /// 地火矩阵 + /// 是否画预警地火 + /// 画多少格预警地火 + /// 初始地火技能施法时间 + /// 地火间隔时间 + /// + private void DrawExaflareScene(Vector3[,] exaflarePos, bool warnDrawn, int advWarnNum, int castTime, int intervalTime, ScriptAccessory accessory) + { + var dirNum = exaflarePos.GetLength(0); + var extNum = exaflarePos.GetLength(1); + + for (var ext = 0; ext < extNum; ext++) + { + // 计算各位置的出现时间与延时时间。往往第一枚地火需要特殊处理,后续采用同时间隔 + var destroy = ext == 0 ? castTime : intervalTime; + var delay= ext == 0 ? 0 : castTime + (ext - 1) * intervalTime; + + if (ext == 0) + { + // 本体地火,对原地的地火(ext=0),只画一个dir=0,不以任何角度向外延伸 + DrawExaflare(exaflarePos[0, ext], delay, destroy, accessory); + DrawExaflareEdge(exaflarePos[0, ext], delay, destroy, accessory); + } + else + { + // 对后续的地火(ext>0),以对应角度向外延伸 + for (var dir = 0; dir < dirNum; dir++) + { + DrawExaflare(exaflarePos[dir, ext], delay, destroy, accessory); + DrawExaflareEdge(exaflarePos[dir, ext], delay, destroy, accessory); + } + } + + if (!warnDrawn) continue; + for (var adv = 1; adv <= advWarnNum; adv++) + { + if (ext >= extNum - adv) continue; + for (var dir = 0; dir < dirNum; dir++) + DrawExaflareWarn(exaflarePos[dir, ext + adv], adv, delay, destroy, intervalTime, accessory); + } + } + } + + /// + /// 构建地火坐标矩阵 + /// + /// 地火本体位置 + /// 一枚地火涉及几个方向 + /// 一枚地火延伸几次 + /// 释放地火幻影旋转角度 + /// 各方向旋转角度 + /// 地火步进延伸距离 + private Vector3[,] BuildExaflareVector(Vector3 sourcePos, int dirNum, int extNum, float sourceRot, float[] flareRot, float extDistance) + { + Vector3[,] exaflarePos = new Vector3[dirNum, extNum]; + if (flareRot.Length != dirNum) return exaflarePos; + for (var ext = 0; ext < extNum; ext++) + for (var dir = 0; dir < dirNum; dir++) + exaflarePos[dir, ext] = sourcePos.ExtendPoint(sourceRot.Game2Logic() + flareRot[dir], ext * extDistance); + return exaflarePos; + } + + private void DrawExaflare(Vector3 spos, int delay, int destroy, ScriptAccessory accessory) + { + const int scale = 6; + var color = ExaflareBuiltInColor ? ColorHelper.ColorExaflare.V4 : ExaflareColor.V4.WithW(1f); + var dp = accessory.DrawStaticCircle(spos, color, delay, destroy, $"地火{spos}", scale); + dp.ScaleMode |= ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void DrawExaflareEdge(Vector3 spos, int delay, int destroy, ScriptAccessory accessory) + { + const float scale = 6; + // const float innerScale = scale - 0.05f; + var color = ExaflareBuiltInColor ? ColorHelper.ColorExaflare.V4 : ExaflareColor.V4.WithW(1.5f); + var dp = accessory.DrawStaticDonut(spos, color, delay, destroy, $"地火边缘{spos}", scale); + // dp.Color = ColorHelper.colorDark.V4; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Donut, dp); + } + + private void DrawExaflareWarn(Vector3 spos, int adv, int delay, int destroy, int interval, ScriptAccessory accessory) + { + const int scale = 6; + var destroyItv = interval * (adv - 1); + var color = ExaflareBuiltInColor ? ColorHelper.ColorExaflareWarn.V4.WithW(1f / adv) : ExaflareWarnColor.V4.WithW(1f / adv); + var dp = accessory.DrawStaticCircle(spos, color, delay, destroy + destroyItv, $"地火预警{spos}", scale); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void DebugExaflare(float[] srot, float bossRotRad, uint bladeType, ScriptAccessory accessory) + { + accessory.Log.Debug($"你选择的策略是{ExaflareStrategy}"); + + List scoreList = ExaflareStrategy switch + { + // moveStep,isFront,isUniverse + ExaflareSpecStrategyEnum.绝不去前方_NeverFront => [2, 100, 50], + ExaflareSpecStrategyEnum.绝不跑无脑火_NeverUniverse => [2, 10, 100], + ExaflareSpecStrategyEnum.绝不多跑_LeastMovement => [20, 10, 50], + ExaflareSpecStrategyEnum.绝对前方_AlwaysFront => [2, -10, 50], + _ => [-10, 100, 0], + }; + _p7Exaflare = new DsrExaflare(scoreList); + + // 面相为前、左、右的扩散 + // var spos = @event.SourcePosition(); + // var srot = @event.SourceRotation(); + Vector3[] spos = + [ + _center.ExtendPoint(bossRotRad.Game2Logic() - float.Pi, 8), + _center.ExtendPoint(bossRotRad.Game2Logic() + 60f.DegToRad(), 8), + _center.ExtendPoint(bossRotRad.Game2Logic() - 60f.DegToRad(), 8) + ]; + var bossChara = accessory.GetById(_p7BossId); + var bossRot = bossChara?.Rotation ?? bossRotRad; + var bossPos = bossChara?.Position ?? _center; + const int intervalTime = 1900; + const int castTime = 6900; + const int extendDistance = 7; + const int dirNum = 3; + const int extNum = 6; + const int advWarnNum = 1; // 预警向外延伸几个 + float[] flareRot = [0, -float.Pi / 2, float.Pi / 2]; + + for (int i = 0; i < 3; i++) + { + Vector3[,] exaflarePos = BuildExaflareVector(spos[i], dirNum, extNum, srot[i], flareRot, extendDistance); + // 画地火箭头 + var dp1 = accessory.DrawDirPos2Pos(spos[i], spos[i].ExtendPoint(srot[i].Game2Logic() + flareRot[0], 6), 0, castTime, $"箭头1", 5.9f); + var dp2 = accessory.DrawDirPos2Pos(spos[i], spos[i].ExtendPoint(srot[i].Game2Logic() + flareRot[1], 6), 0, castTime, $"箭头2", 5.9f); + var dp3 = accessory.DrawDirPos2Pos(spos[i], spos[i].ExtendPoint(srot[i].Game2Logic() + flareRot[2], 6), 0, castTime, $"箭头3", 5.9f); + dp1.Color = ColorHelper.ColorRed.V4; + dp2.Color = ColorHelper.ColorRed.V4; + dp3.Color = ColorHelper.ColorRed.V4; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + + DrawExaflareScene(exaflarePos, ExaflareWarnDrawn, advWarnNum, castTime, intervalTime, accessory); + if (_p7Exaflare == null) return; + lock (_p7Exaflare) + { + _p7Exaflare.SetBossPos(bossPos, accessory); + _p7Exaflare.AddExaflare(spos[i], bossRot, srot[i], accessory); + } + } + _p7Exaflare.SetBladeType(bladeType); + switch (bladeType) + { + case ChariotBlade: + var dp1 = accessory.DrawStaticCircle(_center, accessory.Data.DefaultDangerColor.WithW(2f), 0, castTime, $"钢铁", 8f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + break; + case ChariotBlade + 1: + var dp2 = accessory.DrawStaticDonut(_center, accessory.Data.DefaultDangerColor.WithW(2f), 0, castTime, $"月环", 50f, 8f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp2); + break; + } + + // 记录完钢铁月环后可计算 + if (_p7Exaflare == null) return; + // if (!IsExaflarePhase()) return; + if (ExaflareStrategy == ExaflareSpecStrategyEnum.关闭_PleaseDontDoThat) return; + if (!_p7Exaflare.ExaflareRecordComplete()) return; + var guidePosList = _p7Exaflare.ExportExaflareSolution(accessory); + DrawExaflareGuidePos(guidePosList, accessory); + } + + [ScriptMethod(name: "忆罪宫地火模拟器", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=Exaflare"], userControl: false)] + public void ExaflareEchoDebug(Event @event, ScriptAccessory accessory) + { + // ---- DEBUG CODE ---- + + _center = new Vector3(400, -54.97f, -400); + Random random = new Random(); + float bossRotLogicDeg = random.Next(0, 360); + var bossRotLogicRad = bossRotLogicDeg.DegToRad(); + accessory.Log.Debug($"随机到的Boss面向为{bossRotLogicRad.RadToDeg()}"); + float[] srot = + [ + (random.Next(0, 8) * float.Pi / 4 + bossRotLogicRad).Logic2Game(), + (random.Next(0, 8) * float.Pi / 4 + bossRotLogicRad).Logic2Game(), + (random.Next(0, 8) * float.Pi / 4 + bossRotLogicRad).Logic2Game() + ]; + Vector3 bossFace = _center.ExtendPoint(bossRotLogicRad, 8f); + var dp = accessory.DrawDirPos2Pos(_center, bossFace, 0, 7000, $"面相", 7.9f); + dp.Color = ColorHelper.ColorDark.V4; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + DebugExaflare(srot, bossRotLogicRad.Logic2Game(), (uint)random.Next(0, 2) + ChariotBlade, accessory); + // -- DEBUG CODE END -- + } + + #endregion P7 地火 + + #region P7 接刀 + + + [ScriptMethod(name: "P7:阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(2805[179]|28206)$"], userControl: false)] + public void P7_PhaseRecord(Event @event, ScriptAccessory accessory) + { + _dsrPhase = _dsrPhase switch + { + DsrPhase.Phase6Cauterize => DsrPhase.Phase7Exaflare1, + DsrPhase.Phase7Exaflare1 => DsrPhase.Phase7Stack1, + DsrPhase.Phase7Stack1 => DsrPhase.Phase7Nuclear1, + DsrPhase.Phase7Nuclear1 => DsrPhase.Phase7Exaflare2, + DsrPhase.Phase7Exaflare2 => DsrPhase.Phase7Stack2, + DsrPhase.Phase7Stack2 => DsrPhase.Phase7Nuclear2, + DsrPhase.Phase7Nuclear2 => DsrPhase.Phase7Exaflare3, + DsrPhase.Phase7Exaflare3 => DsrPhase.Phase7Stack3, + DsrPhase.Phase7Stack3 => DsrPhase.Phase7Enrage, + _ => DsrPhase.Phase7Exaflare1, + }; + accessory.Log.Debug($"当前阶段为:{_dsrPhase}"); + + if (!_p7FirstEnmityOrder.Contains(true)) + { + // 初始化 + _p7FirstEnmityOrder = [true, false]; + _p7TrinityDisordered = false; + _p7TrinityTankDisordered = false; + _p7TrinityNum = 0; + } + else + { + _p7FirstEnmityOrder[0] = !_p7FirstEnmityOrder[0]; + _p7FirstEnmityOrder[1] = !_p7FirstEnmityOrder[1]; + accessory.Log.Debug($"MT为{(_p7FirstEnmityOrder[0] ? "一仇" : "二仇")},ST为{(_p7FirstEnmityOrder[1] ? "一仇" : "二仇")}"); + } + _trinityEvent.Set(); + + if (!IsStackPhase()) return; + List scoreList = ExaflareStrategy switch + { + // moveStep,isFront,isUniverse + ExaflareSpecStrategyEnum.绝不去前方_NeverFront => [2, 100, 50], + ExaflareSpecStrategyEnum.绝不跑无脑火_NeverUniverse => [2, 10, 100], + ExaflareSpecStrategyEnum.绝不多跑_LeastMovement => [20, 10, 50], + ExaflareSpecStrategyEnum.绝对前方_AlwaysFront => [2, -10, 50], + _ => [-10, 100, 0], + }; + _p7Exaflare = new DsrExaflare(scoreList); + + } + + private bool IsExaflarePhase() + { + return _dsrPhase is DsrPhase.Phase7Exaflare1 or DsrPhase.Phase7Exaflare2 or DsrPhase.Phase7Exaflare3; + } + + private bool IsStackPhase() + { + return _dsrPhase is DsrPhase.Phase7Stack1 or DsrPhase.Phase7Stack2 or DsrPhase.Phase7Stack3; + } + + [ScriptMethod(name: "三剑一体接刀", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(2805[179])$"])] + public void P7_TrinityAttack(Event @event, ScriptAccessory accessory) + { + _trinityEvent.WaitOne(); + var aid = @event.ActionId(); + var sid = @event.SourceId(); + const uint exaflare = 28059; + const uint stack = 28051; + const uint nuclear = 28057; + + var delay = aid switch + { + exaflare => 15200, + stack => 18500, + nuclear => 27200, + _ => 0 + }; + + delay = _dsrPhase switch + { + DsrPhase.Phase7Stack1 => delay, + DsrPhase.Phase7Stack2 => delay + 1100, + DsrPhase.Phase7Stack3 => delay + 2200, + _ => delay + }; + + DrawTrinityAggro(sid, delay - 4000, 4000, 1, accessory); + DrawTrinityAggro(sid, delay - 4000, 4000, 2, accessory); + DrawTrinityAggro(sid, delay, 4000, 1, accessory); + DrawTrinityAggro(sid, delay, 4000, 2, accessory); + DrawTrinityNear(sid, delay - 4000, 4000, accessory); + DrawTrinityNear(sid, delay, 4000, accessory); + _trinityEvent.Reset(); + } + + private void DrawTrinityAggro(uint sid, int delay, int destroy, uint aggroIdx, ScriptAccessory accessory) + { + var myIndex = accessory.GetMyIndex(); + Vector4 color; + + if (myIndex > 1 || _p7TrinityTankDisordered) + color = accessory.Data.DefaultDangerColor; + else + { + switch (_p7FirstEnmityOrder[myIndex]) + { + case true when aggroIdx == 1: + case false when aggroIdx == 2: + color = accessory.Data.DefaultSafeColor; + break; + default: + color = accessory.Data.DefaultDangerColor; + break; + } + } + + var dp = accessory.DrawOwnersEnmityOrder(sid, aggroIdx, 3f, 3f, delay, destroy, $"三剑一体仇恨{aggroIdx}", byTime: true); + dp.Color = color.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void DrawTrinityNear(uint sid, int delay, int destroy, ScriptAccessory accessory) + { + var myIndex = accessory.GetMyIndex(); + + var dp = accessory.DrawTargetNearFarOrder(sid, 1, true, 3f, 3f, delay, destroy, $"三剑一体近距", byTime: true); + if (_p7TrinityDisordered) + dp.Color = accessory.Data.DefaultDangerColor; + else + dp.Color = myIndex == _p7TrinityOrderIdx[_p7TrinityNum] ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "三剑一体接刀记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:28065"], userControl: false)] + public void P7_TrinityOrderRecord(Event @event, ScriptAccessory accessory) + { + // 主视角为T,忽略脚下接刀 + var myIndex = accessory.GetMyIndex(); + if (myIndex < 2) return; + + var targetIdx = @event.TargetIndex(); + if (targetIdx != 1) + { + if (_p7TrinityDisordered) return; + accessory.Log.Debug($"有人多接了一刀,失效"); + accessory.Method.TextInfo($"有人多接了一刀,不再以安全色提示", 3000, true); + _p7TrinityDisordered = true; + return; + } + + var tid = @event.TargetId(); + var tidx = accessory.GetPlayerIdIndex(tid); + if (_p7TrinityOrderIdx[_p7TrinityNum] != tidx && !_p7TrinityDisordered) + { + accessory.Log.Debug($"接刀人错误,失效"); + accessory.Method.TextInfo($"接刀人错误,不再以安全色提示", 3000, true); + _p7TrinityDisordered = true; + } + + _p7TrinityNum++; + if (_p7TrinityNum >= 6) + _p7TrinityNum = 0; + + var targetRecent = accessory.GetPlayerJobByIndex(tidx); + var targetNext = accessory.GetPlayerJobByIndex(_p7TrinityOrderIdx[_p7TrinityNum]); + accessory.Log.Debug($"刚刚接刀的是{targetRecent},下一个接刀人为{targetNext}"); + } + + [ScriptMethod(name: "三剑一体T刀记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(2806[34])$"], userControl: false)] + public void P7_TrinityTankRecord(Event @event, ScriptAccessory accessory) + { + var aid = @event.ActionId(); + var tid = @event.TargetId(); + + // 非T玩家接到刀 + var tidx = accessory.GetPlayerIdIndex(tid); + if (tidx > 1) return; + + // 主视角不是T + var myIndex = accessory.GetMyIndex(); + if (myIndex > 1) return; + + // 已经失效 + if (_p7TrinityTankDisordered) return; + + const uint aggro1 = 28063; + const uint aggro2 = 28064; + + // 一仇效果,但目标是二仇 || 二仇效果,但目标是一仇 + if ((_p7FirstEnmityOrder[tidx] || aid != aggro1) && (!_p7FirstEnmityOrder[tidx] || aid != aggro2)) return; + accessory.Log.Debug($"接刀仇恨错误,失效"); + accessory.Method.TextInfo($"接刀仇恨错误,不再以安全色提示", 3000, true); + _p7TrinityTankDisordered = true; + } + + #endregion P7 接刀 + 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) + { + sa = accessory; + Priorities = new Dictionary(); + ActionCount = 0; + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + } + + /// + /// 为特定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, 8, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, 8, 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; + } + } +} + +#region Class 地火 + +public class DsrExaflare(List scoreList) +{ + // 右上0,下1,左2 + private List ExaflarePosList { get; set; } = Enumerable.Repeat(new Vector3(0, 0, 0), 3).ToList(); + private Vector3 BossPos { get; set; } = new Vector3(0, 0, 0); + private List ExaflareDirList { get; set; } = [0, 0, 0]; + private uint BladeType { get; set; } = 0; + private List ExaflareSolutionList { get; set; } = []; + public int RecordedExaflareNum = 0; + + private ExaflareSolution BuildOneStepSolutionNew(ScriptAccessory accessory) + { + // 一步火 + const bool isUniverse = false; + var moveStep = 0; + Vector3 pos2; + Vector3 pos3; + int targetExaflareIdx; + var debugText = $"[a][一步火]: \n"; + + if (!IsFrontPointedByExaflare(0)) + targetExaflareIdx = 0; + else if (!IsFrontPointedByExaflare(2)) + targetExaflareIdx = 2; + else + { + targetExaflareIdx = 0; + moveStep++; + } + + pos2 = ExaflarePosList[targetExaflareIdx]; + + if (moveStep == 0) + { + debugText += $"[a]检测到{GetExaflareIdxStr(targetExaflareIdx)}地火未被指向,可作为安全点\n"; + pos3 = pos2; + } + else + { + debugText += $"[a]检测到前方地火均被指向,走前方两步火,随便取左上作安全点\n"; + pos3 = ExaflarePosList[1].PointInOutside(BossPos, 12f); + } + + // pos1 根据职能定义起跑点 + var myIndex = accessory.GetMyIndex(); + var pos1 = FindFirstSafePosAtFront(targetExaflareIdx, myIndex < 1); + debugText += $"[a]玩家序号为{myIndex}, 为{(myIndex < 1?"坦克":"人群")}视角,\n倾向于{(myIndex < 1?"前方":"后方")}就位\n"; + moveStep++; + + accessory.Log.Debug(debugText); + + return new ExaflareSolution([pos1, pos2, pos3], moveStep, true, isUniverse, "一步火", scoreList, + accessory); + } + + private ExaflareSolution BuildTwoStepSolution(ScriptAccessory accessory) + { + // 两步火 + var backExaflarePos = ExaflarePosList[1]; + var isUniverse = false; + var moveStep = 0; + // pos1 读条时,找背后地火的钢铁月环安全区 + var pos1 = FindFirstSafePos(1, true); + moveStep++; + // pos2 一炸后,找背后地火位置 + var pos2 = backExaflarePos; + // pos3 二炸后,观察前面两枚 + Vector3 pos3; + var debugText = $"[b][两步火]: \n"; + + // 前方两地火是否指向背后 + var idx0Point = IsBackPointedByExaflare(0); + var idx2Point = IsBackPointedByExaflare(2); + + if (!idx0Point && !idx2Point) + { + // 都未指向背后,原地 + pos3 = backExaflarePos; + debugText += $"[b]检测到前方地火前方地火都未指向背后,转为背后一步火\n"; + } + else if (!idx0Point && idx2Point) + { + // 右上未指向背后,去左侧 + pos3 = backExaflarePos.RotatePoint(BossPos, 45f.DegToRad()); + moveStep++; + debugText += $"[b]检测到右上地火未指向背后,去左后\n"; + } + else if (idx0Point && !idx2Point) + { + // 左上未指向背后,去右侧 + pos3 = backExaflarePos.RotatePoint(BossPos, -45f.DegToRad()); + moveStep++; + debugText += $"[b]检测到左上地火未指向背后,去右侧\n"; + } + else + { + // 全部指向背后,无脑火 + pos3 = FindUniversalSafePos(); + isUniverse = true; + moveStep++; + debugText += $"[b]检测到地火全指向背后,转为无脑火\n"; + } + accessory.Log.Debug(debugText); + return new ExaflareSolution([pos1, pos2, pos3], moveStep, false, isUniverse, "两步火", scoreList, + accessory); + } + + /// + /// 以某一枚地火开始,顺时针或逆时针处就位 + /// + /// 某一枚地火 + /// 顺时针找 + /// + private Vector3 FindFirstSafePos(int exaflareIdx, bool isCw) + { + var exaflarePos = ExaflarePosList[exaflareIdx]; + var rad = exaflarePos.FindRadian(BossPos) + (isCw ? 50f.DegToRad() : -50f.DegToRad()); + var firstSafePos = BossPos.ExtendPoint(rad, IsChariot() ? 8.5f : 7.5f); + return firstSafePos; + } + + private Vector3 FindFirstSafePosAtFront(int exaflareIdx, bool isTank) + { + // var exaflarePos = ExaflarePosList[exaflareIdx]; + if (isTank) // 是坦克,则前方起跑 + { + if (exaflareIdx == 0) + return FindFirstSafePos(exaflareIdx, false); + if (exaflareIdx == 2) + return FindFirstSafePos(exaflareIdx, true); + } + else + { + if (exaflareIdx == 0) + return FindFirstSafePos(exaflareIdx, true); + if (exaflareIdx == 2) + return FindFirstSafePos(exaflareIdx, false); + } + return new Vector3(0, 0, 0); + } + + private Vector3 FindUniversalSafePos() + { + return ExaflarePosList[1].PointInOutside(BossPos, 13.2f - 8f, true); + } + + public void SetBossPos(Vector3 bossPosV3, ScriptAccessory accessory) + { + BossPos = bossPosV3; + // accessory.DebugMsg($"设置Boss位置{BossPos}", debugMode); + } + + /// + /// 增加地火属性 + /// + /// 地火位置 + /// Boss旋转角度 + /// 地火旋转角度 + /// + public void AddExaflare(Vector3 exaflarePosV3, float bossRotation, float exaflareRot, ScriptAccessory accessory) + { + var idx = FindExaflareIdx(exaflarePosV3, bossRotation); + // 差值无需互转 + var exaflareRelativeDir = exaflareRot.Game2Logic() - bossRotation.Game2Logic(); + var dir = exaflareRelativeDir.Rad2Dirs(8); + ExaflareDirList[idx] = dir; + ExaflarePosList[idx] = exaflarePosV3; + accessory.Log.Debug($"添加{GetExaflareIdxStr(idx)}地火,坐标{exaflarePosV3},面向{GetDirStr(dir)}"); + RecordedExaflareNum++; + } + + /// + /// 根据地火中心位置找到对应地火本体方位的idx + /// 因为地火位置会根据Boss面向改变,所以要减去boss旋转的偏置量 + /// + /// 地火中心位置 + /// Boss面向 + /// + private int FindExaflareIdx(Vector3 exaflarePosV3, float bossRotation) + { + var exaflareBaseDir = exaflarePosV3.FindRadian(BossPos); + var exaflareRelativeDir = exaflareBaseDir - bossRotation.Game2Logic(); + var idx = exaflareRelativeDir.Rad2Dirs(3, false); + return idx; + } + + /// + /// 返回该枚地火是否为正角,当八方方位为偶数时是正角 + /// + /// 某枚地火 + /// + private bool IsExaflareRightDir(int idx) + { + return ExaflareDirList[idx] % 2 == 0; + } + + /// + /// 找到背后是否被序号为idx的地火指 + /// + /// 地火序号 + /// + private bool IsBackPointedByExaflare(int idx) + { + // 右上地火指向背后地火的条件:右上地火不是正火且方向不等于1 + // 左上地火指向背后地火的条件:左上地火不是正火且方向不等于7 + var result = idx switch + { + 0 => !IsExaflareRightDir(idx) && ExaflareDirList[idx] != 1, + 2 => !IsExaflareRightDir(idx) && ExaflareDirList[idx] != 7, + _ => false + }; + return result; + } + + /// + /// 找到前方序号为idx的地火是否被指 + /// + /// 地火序号 + /// + private bool IsFrontPointedByExaflare(int idx) + { + // 右上地火被指:左上地火为正火,且方向不为6(朝左) 或 背后地火是斜火,且方向不为5(朝左下) + // 左上地火被指:右上地火为正火,且方向不为2(朝右) 或 背后地火是斜火,且方向不为3(朝右下) + var result = idx switch + { + 0 => (IsExaflareRightDir(2) && ExaflareDirList[2] != 6) || + (!IsExaflareRightDir(1) && ExaflareDirList[1] != 5), + 2 => (IsExaflareRightDir(0) && ExaflareDirList[0] != 2) || + (!IsExaflareRightDir(1) && ExaflareDirList[1] != 3), + _ => false + }; + return result; + } + + public void SetBladeType(uint type) + { + BladeType = type; + } + + private bool IsChariot() + { + const uint chariotFireBlade = 298; + return BladeType == chariotFireBlade; + } + + private void AddExaflareSolution(ExaflareSolution solution) + { + ExaflareSolutionList.Add(solution); + } + + public List ExportExaflareSolution(ScriptAccessory accessory) + { + AddExaflareSolution(BuildOneStepSolutionNew(accessory)); + AddExaflareSolution(BuildTwoStepSolution(accessory)); + + ExaflareSolutionList = ExaflareSolutionList.OrderBy(solution => solution.Score).ToList(); + accessory.Log.Debug($"两解法对比,优先级高的是{ExaflareSolutionList[0].Description},为{ExaflareSolutionList[0].Score}分"); + return ExaflareSolutionList[0].ExaflareSolutionPosList; + } + + /* + * 下述为构建地火的方法,今后可以单独做成一个class调用 + */ + + // /// + // /// 构建地火坐标 + // /// + // /// 中心 + // /// 旋转角度 + // /// 延伸距离 + // /// + // private Vector3 GetExaflarePos(Vector3 center, float rotation, float extendDistance) + // { + // return center.ExtendPoint(rotation, extendDistance); + // } + + // private Vector3[] BuildExaflareVector(Vector3 center, float rotation, int extendNum, float extendDistance) + // { + // var exaflarePos = new Vector3[extendNum]; + // for (var i = 0; i < extendNum; i++) + // exaflarePos[i] = GetExaflarePos(center, rotation, (i + 1) * extendDistance); + // return exaflarePos; + // } + + public bool ExaflareRecordComplete() + { + return RecordedExaflareNum == 3; + } + + private string GetExaflareIdxStr(int idx) + { + return idx switch + { + 0 => "右上", + 1 => "背后", + 2 => "左上", + _ => "未知" + }; + } + + private string GetDirStr(int idx) + { + return idx switch + { + 0 => "正上", + 1 => "右上", + 2 => "正右", + 3 => "右下", + 4 => "正下", + 5 => "左下", + 6 => "正左", + 7 => "左上", + _ => "未知" + }; + } + + public class ExaflareSolution + { + /* + * 地火优选策略 + * 地火共有四种解法选项: + * 1、绝不去前方 NeverFront + * 背后两步火>无脑火>>>前方一步火>前方两步火 + * 2、绝不跑无脑火 NeverUniverse + * 背后两步火>前方一步火>前方两步火>>>无脑火 + * 3、绝不多跑 LeastMovement + * 前方一步火>背后两步火>前方两步火>>>无脑火 + * 4、绝对前方 AlwaysFront + * 前方一步火>前方两步火>背后两步火>无脑火 + * 四种解法被求解后,分数低者取胜。 + * + * 解法情况与影响分值的对应关系 + * basic moveStep isFront isUniverse + * NeverFront 100 2 100 50 + * NeverUniverse 100 2 10 100 + * LeastMovement 100 20 10 50 + * AlwaysFront 100 2 -10 50 + */ + public List ExaflareSolutionPosList { get; set; } + public int MoveStep { get; set; } + public bool IsFront { get; set; } + public bool IsUniverse { get; set; } + public int Score { get; set; } + public string Description { get; set; } + + public ExaflareSolution(List exaflareSolutionPosList, int moveStep, bool isFront, bool isUniverse, + string description, List scoreList, ScriptAccessory accessory) + { + ExaflareSolutionPosList = exaflareSolutionPosList; + MoveStep = moveStep; + IsFront = isFront; + IsUniverse = isUniverse; + Score = CalcScore(scoreList, accessory, description); + Description = description; + } + private int CalcScore(List scoreList, ScriptAccessory accessory, string description) + { + const int moveStepIdx = 0; + const int isFrontIdx = 1; + const int isUniverseIdx = 2; + const int baseScore = 100; + var moveStepScore = scoreList[moveStepIdx] * MoveStep; + var isFrontScore = IsFront ? scoreList[isFrontIdx] : 0; + var isUniverseScore = IsUniverse ? scoreList[isUniverseIdx] : 0; + var totalScore = baseScore + moveStepScore + isFrontScore + isUniverseScore; + accessory.Log.Debug( + $"{description}的得分为:基础{baseScore} + 步数{moveStepScore} + 前方{isFrontScore} + 无脑{isUniverseScore} = {totalScore}"); + return totalScore; + } + } +} + +#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 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 uint TargetIndex(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetIndex"]); + } + + 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static uint Id(this Event @event) + { + return ParseHexId(@event["Id"], 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 id) + { + return sa.Data.Objects.SearchById(id); + } +} + +public static class DirectionCalc +{ + 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建立list + // Game List Logic + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Logic = Pi - Game (+ 2pi) + + /// + /// 将游戏基角度(以南为0,逆时针增加)转为逻辑基角度(以北为0,顺时针增加) + /// 算法与Logic2Game完全相同,但为了代码可读性,便于区分。 + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float Game2Logic(this float radian) + { + // if (r < 0) r = (float)(r + 2 * Math.PI); + // if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + + var r = float.Pi - radian; + r = (r + float.Pi * 2) % (float.Pi * 2); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// 算法与Game2Logic完全相同,但为了代码可读性,便于区分。 + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float Logic2Game(this float radian) + { + // var r = (float)Math.PI - radian; + // if (r < Math.PI) r = (float)(r + 2 * Math.PI); + // if (r > Math.PI) r = (float)(r - 2 * Math.PI); + + return radian.Game2Logic(); + } + + /// + /// 输入逻辑基角度,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 逻辑基角度 + /// 方位总数 + /// 斜分割,默认true + /// 逻辑基角度对应的逻辑方位 + public static int Rad2Dirs(this float radian, int dirs, bool diagDivision = true) + { + var r = diagDivision + ? Math.Round(radian / (2f * float.Pi / dirs)) + : Math.Floor(radian / (2f * float.Pi / dirs)); + r = (r + dirs) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 斜分割,默认true + /// 该坐标点对应的逻辑方位 + public static int Position2Dirs(this Vector3 point, Vector3 center, int dirs, bool diagDivision = true) + { + double dirsDouble = dirs; + var r = diagDivision + ? Math.Round(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble + : Math.Floor(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble; + return (int)r; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(this Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new Vector3(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + + // 另一种方案待验证 + // var nextPos = Vector3.Transform((point - center), Matrix4x4.CreateRotationY(radian)) + center; + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(this Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new Vector3(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(this Vector3 newPoint, Vector3 center) + { + // 找到某点到中心的弧度 + float radian = MathF.PI - MathF.Atan2(newPoint.X - center.X, newPoint.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointHorizon(this Vector3 point, float centerX) + { + // Vector3 v3 = new(2 * centerX - point.X, point.Y, point.Z); + // return v3; + return point with { X = 2 * centerX - point.X }; + } + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointVertical(this Vector3 point, float centerZ) + { + // Vector3 v3 = new(point.X, point.Y, 2 * centerZ - point.Z); + // return v3; + return point with { Z = 2 * centerZ - point.Z }; + } + + /// + /// 将输入点朝某中心点往内/外同角度延伸,默认向内 + /// + /// 待延伸点 + /// 中心点 + /// 延伸长度 + /// 是否向外延伸> + /// + public static Vector3 PointInOutside(this Vector3 point, Vector3 center, float length, bool isOutside = false) + { + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var targetPos = (point - center) / v2.Length() * length * (isOutside ? 1 : -1) + point; + return targetPos; + } + + /// + /// 寻找两点之间的角度差,范围0~360deg + /// + /// 基准位置 + /// 比较目标位置 + /// 场地中心 + /// + public static float FindRadianDifference(this Vector3 targetPos, Vector3 basePoint, Vector3 center) + { + var baseRad = basePoint.FindRadian(center); + var targetRad = targetPos.FindRadian(center); + var deltaRad = targetRad - baseRad; + if (deltaRad < 0) + deltaRad += float.Pi * 2; + return deltaRad; + } + + /// + /// 从场中看向场外是否在右侧,多用于场边敌人的分身机制 + /// + /// 基准位置 + /// 比较目标位置 + /// 场地中心 + /// + public static bool IsAtRight(this Vector3 targetPos, Vector3 basePoint, Vector3 center) + { + // 从场中看向场外,在右侧 + return targetPos.FindRadianDifference(basePoint, center) < float.Pi; + } +} + +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory accessory, ulong pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf((uint)pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory accessory, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = accessory.Data.PartyList.IndexOf(pid); + var str = accessory.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory accessory, int idx) + { + var str = idx switch + { + 0 => "MT", + 1 => "ST", + 2 => "H1", + 3 => "H2", + 4 => "D1", + 5 => "D2", + 6 => "D3", + 7 => "D4", + _ => "unknown" + }; + return str; + } +} + +public static class ColorHelper +{ + public static ScriptColor ColorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor ColorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + public static ScriptColor ColorDark = new ScriptColor { V4 = new Vector4(0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorLightBlue = new ScriptColor { V4 = new Vector4(0.48f, 0.40f, 0.93f, 1.0f) }; + public static ScriptColor ColorWhite = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; + public static ScriptColor ColorYellow = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + public static ScriptColor ColorExaflare = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0.0f, 1.5f) }; + public static ScriptColor ColorExaflareWarn = new ScriptColor { V4 = new Vector4(0.6f, 0.6f, 1f, 1.0f) }; +} + +public static class ListHelper +{ + /// + /// 将List转为String以输出 + /// + /// + /// + /// + public static string StringList(this List list) + { + return string.Join(", ", list); + } +} + +public static class AssignDp +{ + /// + /// 返回箭头指引相关dp + /// + /// + /// 箭头起始,可输入uint或Vector3 + /// 箭头指向目标,可输入uint或Vector3,为0则无目标 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 箭头旋转角度 + /// 箭头宽度 + /// 使用安全色 + /// + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f, bool isSafe = true) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Rotation = rotation; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = isSafe ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + + switch (ownerObj) + { + case uint sid: + dp.Owner = sid; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException("ownerObj的目标类型输入错误"); + } + + switch (targetObj) + { + case uint tid: + if (tid != 0) dp.TargetObject = tid; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + } + + return dp; + } + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f, bool isSafe = true) + { + return targetObj switch + { + uint uintTarget => accessory.DrawGuidance(accessory.Data.Me, uintTarget, delay, destroy, name, rotation, scale, isSafe), + Vector3 vectorTarget => accessory.DrawGuidance(accessory.Data.Me, vectorTarget, delay, destroy, name, rotation, scale, isSafe), + _ => throw new ArgumentException("targetObj 的类型必须是 uint 或 Vector3") + }; + } + + + /// + /// 返回自己指向某目标地点的dp,可修改dp.TargetPosition, dp.Scale + /// + /// 指向地点 + /// 延时delay ms出现 + /// 指路线条宽度 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit DrawDirPos(this ScriptAccessory accessory, Vector3 targetPos, int delay, int destroy, string name, float scale = 1f) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = targetPos; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + /// + /// 返回起始地点指向某目标地点的dp,可修改dp.Position, dp.TargetPosition, dp.Scale + /// + /// 起始地点 + /// 指向地点 + /// 延时delay ms出现 + /// 指路线条宽度 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit DrawDirPos2Pos(this ScriptAccessory accessory, Vector3 startPos, Vector3 targetPos, int delay, int destroy, string name, float scale = 1f) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Position = startPos; + dp.TargetPosition = targetPos; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + /// + /// 返回距离某对象目标最近/最远的dp + /// + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图宽度 + /// 绘图长度 + /// true为最近,false为最远 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// + public static DrawPropertiesEdit DrawTargetNearFarOrder(this ScriptAccessory accessory, uint ownerId, uint orderIdx, + bool isNear, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = + isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回ownerId仇恨相关的dp + /// + /// + /// 起始目标id,通常为boss + /// 仇恨顺序,从1开始 + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawOwnersEnmityOrder(this ScriptAccessory accessory, uint ownerId, uint orderIdx, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 圆圈尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory accessory, uint ownerId, float scale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 绘图中心位置 + /// 图形角度 + /// 旋转角度,以北为0度顺时针 + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit DrawStatic(this ScriptAccessory accessory, Vector3 center, float radian, float rotation, float width, float length, int delay, int destroy, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Position = center; + dp.Radian = radian; + dp.Rotation = rotation.Logic2Game(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + /// + /// 返回静态圆圈dp,通常用于指引固定位置。 + /// + /// + /// 圆圈中心位置 + /// 圆圈颜色 + /// 圆圈尺寸,默认1.5f + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawStaticCircle(this ScriptAccessory accessory, Vector3 center, Vector4 color, int delay, int destroy, string name, float scale = 1.5f) + { + var dp = accessory.DrawStatic(center, 0, 0, scale, scale, delay, destroy, name); + dp.Color = color; + return dp; + } + + /// + /// 返回静态月环dp,通常用于指引固定位置。 + /// + /// + /// 月环中心位置 + /// 月环颜色 + /// 月环外径,默认1.5f + /// 月环内径,默认scale-0.05f + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawStaticDonut(this ScriptAccessory accessory, Vector3 center, Vector4 color, int delay, int destroy, string name, float scale, float innerscale = 0) + { + var dp = accessory.DrawStatic(center, float.Pi * 2, 0, scale, scale, delay, destroy, name); + dp.Color = color; + dp.InnerScale = innerscale != 0f ? new Vector2(innerscale) : new Vector2(scale - 0.05f); + return dp; + } + + /// + /// 返回扇形 + /// + /// 起始目标id,通常为自己或Boss + /// 扇形弧度 + /// 图形旋转角度 + /// 扇形尺寸 + /// 扇形内环空心尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory accessory, uint ownerId, float radian, float rotation, float scale, float innerScale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.InnerScale = new Vector2(innerScale); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } +} + +#endregion + diff --git a/lianying1997/06-EndWalker/Lv87_KtisisHyperboreia.cs b/lianying1997/06-EndWalker/Lv87_KtisisHyperboreia.cs new file mode 100644 index 0000000..9542ae3 --- /dev/null +++ b/lianying1997/06-EndWalker/Lv87_KtisisHyperboreia.cs @@ -0,0 +1,225 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using Lumina.Excel.Sheets; + +namespace UsamisScript; + +[ScriptType(name: "Lv87 [创造环境极北造物院]", territorys: [974], + guid: "8d4e4a9c-b144-4ec2-82cd-46b38867e4e6", version: "0.0.0.2", author: "Usami", note: "仅老二老三,别吃dot了别吃dot了!", updateInfo: UpdateInfo)] +public class KtisisHyperboreia +{ + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + // [UserSetting("启用本体一运塔[小队频道]发宏")] + // public bool HC1_ChatGuidance { get; set; } = false; + + [UserSetting("Debug模式(玩家无需开启)")] + public bool DebugMode { get; set; } = false; + + List Boss2_SafePosition = [false, false, false]; + + public void Init(ScriptAccessory accessory) + { + Boss2_SafePosition = [false, false, false]; + 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; + } + } + + public static int PositionMatchesTo8Dir(Vector3 point, Vector3 center) + { + float x = point.X - center.X; + float z = point.Z - center.Z; + + int direction = (int)Math.Round(4 - 4 * Math.Atan2(x, z) / Math.PI) % 8; + + // Dirs: N = 0, NE = 1, ..., NW = 7 + return (direction + 8) % 8; // 防止负值出现 + } + + // [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + // public void EchoDebug(Event @event, ScriptAccessory accessory) + // { + // var msg = @event["Message"].ToString(); + // accessory.Method.SendChat($"/e 获得玩家发送的消息:{msg}"); + + // var dp = accessory.Data.GetDefaultDrawProperties(); + // dp.Name = $"纯正飙风直线"; + // dp.Scale = new(10, 50); + // dp.Owner = 0x40009B1E; + // dp.Color = accessory.Data.DefaultDangerColor; + // dp.ScaleMode = ScaleMode.ByTime; + // dp.Delay = 0; + // dp.DestoryAt = 3700; + // accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + // } + + // [ScriptMethod(name: "防击退删除画图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(7559|7548|7389)$"], userControl: false)] + // // 沉稳咏唱|亲疏自行|原初的解放 + // public void RemoveLine(Event @event, ScriptAccessory accessory) + // { + // if (!ParseObjectId(@event["SourceId"], out var id)) return; + // if (id == accessory.Data.Me) + // { + // accessory.Method.RemoveDraw("^(可防击退-.*)$"); + // // accessory.Method.SendChat($"/e 检测到防击退,并删除画图"); + // } + // } + + #region 老二:拉冬之王 + + [ScriptMethod(name: "BOSS2:吐息记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(2812|2813|2814)$"], userControl: false)] + // 沉稳咏唱|亲疏自行|原初的解放 + public void Boss2_BreathCollect(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var stid = @event["StatusID"]; + switch (stid) + { + case "2812": + Boss2_SafePosition[0] = true; + if (DebugMode) + accessory.Method.SendChat($"/e [DEBUG]:检测到中间头的吐息……"); + break; + case "2813": + Boss2_SafePosition[1] = true; + if (DebugMode) + accessory.Method.SendChat($"/e [DEBUG]:检测到左边头的吐息……"); + break; + case "2814": + Boss2_SafePosition[2] = true; + if (DebugMode) + accessory.Method.SendChat($"/e [DEBUG]:检测到右边头的吐息……"); + break; + } + } + + [ScriptMethod(name: "BOSS2:吐息绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25734|25735|25736|25737|25738|25739)$"])] + public void Boss2_BreathCall(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + if (DebugMode) + accessory.Method.SendChat($"/e [DEBUG]:检测到中{Boss2_SafePosition[0]},左{Boss2_SafePosition[1]},右{Boss2_SafePosition[2]}"); + + if (Boss2_SafePosition[2]) + { + Boss2_SafePosition[2] = false; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扇形检测-右后"; + dp.Scale = new(30); + dp.Radian = float.Pi * 2 / 3; + dp.Rotation = -float.Pi * 2 / 3; + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 6700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + if (Boss2_SafePosition[1]) + { + Boss2_SafePosition[1] = false; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扇形检测-左后"; + dp.Scale = new(30); + dp.Radian = float.Pi * 2 / 3; + dp.Rotation = float.Pi * 2 / 3; + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 6700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + if (Boss2_SafePosition[0]) + { + Boss2_SafePosition[0] = false; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扇形检测-正面"; + dp.Scale = new(30); + dp.Radian = float.Pi * 2 / 3; + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 6700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + + #endregion + + #region 老三:赫尔墨斯 + + [ScriptMethod(name: "BOSS3:纯正飙风", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(25889|27836)$"])] + public void Boss3_TrueAeroIV(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"纯正飙风直线"; + dp.Scale = new(10, 50); + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Delay = 0; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "BOSS3:四重纯正飙风", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^27837$"])] + public void Boss3_QuadTrueAeroIV(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"四重纯正飙风"; + dp.Scale = new(10, 50); + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode = ScaleMode.ByTime; + dp.Delay = 6000; + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + + #endregion + +} \ No newline at end of file diff --git a/lianying1997/06-EndWalker/Rubicante.cs b/lianying1997/06-EndWalker/Rubicante.cs new file mode 100644 index 0000000..5af7e23 --- /dev/null +++ b/lianying1997/06-EndWalker/Rubicante.cs @@ -0,0 +1,643 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using Lumina.Excel.Sheets; + +namespace UsamisScript.EndWalker.Rubicante; + +[ScriptType(name: "Rubicante [卢比坎特歼灭战]", territorys: [1095], + guid: "dd6bb7eb-c051-4406-8ec3-ff3523c753ee", version: "0.0.0.2", author: "Usami", note: noteStr, updateInfo: UpdateInfo)] + +public class Rubicante +{ + const string noteStr = + """ + v0.0.0.2: + 鸭门。 + """; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public static bool DebugMode { get; set; } = false; + + const uint CLOCKWISE = 0x00020001; // EnvControl State 代表顺时针旋转 + const uint COUNTER = 0x00200010; // EnvControl State 代表逆时针旋转 + const uint INNER = 0x00000001; // EnvControl Index 代表内圈魔法阵 + const uint MIDDLE = 0x00000002; // EnvControl Index 代表中圈魔法阵 + const uint OUTER = 0x00000003; // EnvControl Index 代表外圈魔法阵 + // const uint HALF_CLEAVE = 15759; // 外围魔法阵红色半场刀 SourceDataI + const uint FAN = 15750; // 外围魔法阵蓝色扇形 SourceDataId + const uint INN = 15765; // 内圈魔法阵 SourceDataId + const uint MID = 15766; // 中圈魔法阵 SourceDataId + const uint OUT = 15767; // 外圈魔法阵 SourceDataId + const uint SINGLE_LINE = 542; // EnvControl Param 内圈魔法阵是单线 + const uint V_SHAPE = 543; // EnvControl Param 内圈魔法阵是V字 + const uint DOUBLE_LINE = 544; // EnvControl Param 内圈魔法阵是双线 + const uint NEKO = 545; // EnvControl Param 中圈魔法阵是猫猫 + const uint EIGHTPOS = 546; // EnvControl Param 外圈魔法阵是八方 + + List InnerCircleId = [null, null, null]; + List isCaptured = [false, false, false]; + List InnCircleFaceDir = [0, 0, 0]; // 内魔法阵面向 + List InnCircleType = [0, 0, 0]; // 内圈魔法阵类型 + List RotateCircleDir = [0, 0, 0]; // 大转盘旋转方向,-1逆时针,+1顺时针,0不转/未知方向 + List OuterFanMagic = [false, false, false, false, false, false, false, false]; // 外圈魔法阵分布,是扇形为True,是半场刀为False + static List NekoOutputAtDir0 = [0, 1, -1, 2, 4, 6, -1, 7]; // 当猫猫Dir为0时,输入与输出的响应 + List OuterTargetMagicDir = [-1, -1]; // 大转盘目标 + List IntercardFlags = [0x02000200, 0x00200020, 0x00020002, 0x00800080]; // 先斜角安全State标记 + public void Init(ScriptAccessory accessory) + { + InnerCircleId = [null, null, null]; + isCaptured = [false, false, false]; + + InnCircleFaceDir = [0, 0, 0]; // 内魔法阵面向 + InnCircleType = [0, 0, 0]; // 内魔法阵类型 + + RotateCircleDir = [0, 0, 0]; // 大转盘旋转方向,-1逆时针,+1顺时针,0不转 + OuterFanMagic = [false, false, false, false, false, false, false, false]; // 外圈魔法阵分布,是扇形为True,是半场刀为False + NekoOutputAtDir0 = [0, 1, -1, 2, 4, 6, -1, 7]; // 当猫猫Dir为0时,输入与输出的响应 + OuterTargetMagicDir = [-1, -1]; // 大转盘目标 + + IntercardFlags = [0x02000200, 0x00200020, 0x00020002, 0x00800080]; // 先斜角安全State标记 + + // DebugMsg($"Init Rubicante-Ex Success.", accessory); + + accessory.Method.MarkClear(); + accessory.Method.RemoveDraw(".*"); + } + + #region 大转盘 + [ScriptMethod(name: "炼狱魔阵外圈类型记录(不可控)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:15750"], userControl: false)] + public void MagicCircleOuterTypeRecord(Event @event, ScriptAccessory accessory) + { + // 15750 蓝 扇形 + var srot = @event.SourceRotation(); + var sdid = @event.SourceDataId(); + var sdir = DirectionCalc.DirRadRoundToDirs(DirectionCalc.BaseInnGame2DirRad(srot), 8); + OuterFanMagic[sdir] = sdid == FAN ? true : false; + } + + [ScriptMethod(name: "炼狱魔阵内圈旋转方向记录(不可控)", eventType: EventTypeEnum.EnvControl, eventCondition: ["Index:regex:^(0000000[123])$"], userControl: false)] + public void MagicCircleRotationRecord(Event @event, ScriptAccessory accessory) + { + var idx = @event.Index(); + var rotate_dir = @event.State(); + int mcidx; + int mcdir; + string log = ""; + + // 定义 idx 和 rotate_dir 的映射关系 + var idxMapping = new Dictionary + { + { INNER, (0, "内环转动,") }, + { MIDDLE, (1, "中环转动,") }, + { OUTER, (2, "外环转动,") } + }; + + var dirMapping = new Dictionary + { + { COUNTER, (-1, "逆时针") }, + { CLOCKWISE, (1, "顺时针") } + }; + + if (idxMapping.ContainsKey(idx)) + { + var (_mcidx, _log) = idxMapping[idx]; + log = log + _log; + mcidx = _mcidx; + } + else + { + log = log + "未知环转动,"; + mcidx = 0; + } + + if (dirMapping.ContainsKey(rotate_dir)) + { + var (_mcdir, _log) = dirMapping[rotate_dir]; + log = log + _log; + mcdir = _mcdir; + } + else + { + log = log + "未知方向"; + mcdir = 0; + } + RotateCircleDir[mcidx] = mcdir; + DebugMsg(log, accessory); + } + + [ScriptMethod(name: "炼狱魔阵内圈类型与ID记录(不可控)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["Param:regex:^(54[23456])$"], userControl: false)] + public void MagicCircleTypeRecord(Event @event, ScriptAccessory accessory) + { + // 内为单线时,其面向与单线一致 + // 内为V字时,其面向与右侧(+2)方向有线 + // 内为双线时,其面向与后侧(+4)方向有线 + // 中为猫猫头时,其面向与猫耳朵相反 + // 外为八方,无人在意 + + var param = @event.Param(); + var tid = @event.TargetId(); + + var paramMapping = new Dictionary + { + { SINGLE_LINE, (0, "内圈", "单线") }, + { V_SHAPE, (0, "内圈", "V字") }, + { DOUBLE_LINE, (0, "内圈", "双线") }, + { NEKO, (1, "中圈", "猫猫")}, + { EIGHTPOS, (2, "外圈", "八方")} + }; + if (!paramMapping.ContainsKey(param)) return; + var (_idx, _pos, _type) = paramMapping[param]; + + // 记录ID + if (!isCaptured[_idx]) + { + isCaptured[_idx] = true; + InnerCircleId[_idx] = (IBattleChara?)accessory.GetById(tid); + DebugMsg($"确定{_pos}ID:{tid}", accessory); + } + + var rot = InnerCircleId[_idx]?.Rotation ?? 0f; + var logic_dir = DirectionCalc.DirRadRoundToDirs(DirectionCalc.BaseInnGame2DirRad((float)rot), 8); + InnCircleType[_idx] = param; + InnCircleFaceDir[_idx] = logic_dir; + DebugMsg($"检测到{_pos}魔法阵类型:{_type},逻辑方向{logic_dir}", accessory); + } + + [ScriptMethod(name: "大转盘算法", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31940|33000)$"], userControl: false)] + public void PurgationAlgorithm(Event @event, ScriptAccessory accessory) + { + Task.Delay(100).ContinueWith(t => + { + // 基于内圈类型、内圈面向与内圈旋转方向获得起始点 + var startPos = getFireStartPos(InnCircleType[0], InnCircleFaceDir[0] + RotateCircleDir[0]); + string startPosStr = string.Join(", ", startPos); + DebugMsg($"- 检测到起始点:{startPosStr}", accessory); + + // 基于中圈猫猫的面向与旋转方向获得猫猫头响应 + List NekoResponse = rotateNekoResponse(InnCircleFaceDir[1] + RotateCircleDir[1]); + string NekoTarget = string.Join(", ", NekoResponse); + DebugMsg($"- 检测到猫猫头响应:{NekoTarget}", accessory); + + for (int i = 0; i < 2; i++) + { + if (startPos[i] != -1) + { + OuterTargetMagicDir[i] = NekoResponse[startPos[i]]; + } + } + string outerTarget = string.Join(", ", OuterTargetMagicDir); + DebugMsg($"- 检测到大转盘目标:{outerTarget}", accessory); + + // 根据转向,更改外圈魔法阵的目标 + int count = OuterFanMagic.Count(); + string RotateCircleDirStr = string.Join(", ", RotateCircleDir); + DebugMsg($"- 检测到大转盘转动数据:{RotateCircleDirStr}", accessory); + + var RotateParam = RotateCircleDir[2] == -1 ? 7 : RotateCircleDir[2]; + List OuterFanMagicBias = new List(OuterFanMagic.Skip(count - RotateParam).Concat(OuterFanMagic.Take(count - RotateParam))); + for (int i = 0; i < 2; i++) + { + if (OuterTargetMagicDir[i] != -1) + { + drawOuterMagic(OuterTargetMagicDir[i], OuterFanMagicBias[OuterTargetMagicDir[i]], accessory); + } + } + + }); + } + + public static void drawOuterMagic(int outDir, bool magicTypeIsFan, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + float radian = (float)Math.PI / 4 * outDir; + + dp.Name = $"外圈魔法阵{outDir}"; + dp.Position = DirectionCalc.RotatePoint(new(100, 0, 80), new(100, 0, 100), radian); + dp.TargetPosition = new(100, 0, 100); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 20000; + + dp.Scale = magicTypeIsFan ? new(45f) : new(45, 20); + dp.Radian = magicTypeIsFan ? (float)Math.PI / 3 : (float)Math.PI * 2; + accessory.Method.SendDraw(DrawModeEnum.Imgui, magicTypeIsFan ? DrawTypeEnum.Fan : DrawTypeEnum.Rect, dp); + } + + public static int[] getFireStartPos(uint innType, int innDir) + { + int startPos1; + int startPos2; + switch (innType) + { + case INN: + startPos1 = innDir; + startPos2 = -1; + break; + case SINGLE_LINE: + startPos1 = innDir; + startPos2 = -1; + break; + case V_SHAPE: + startPos1 = innDir; + startPos2 = (innDir + 2) % 8; + // startPos2 = (innDir + 2) >= 8 ? innDir - 6 : innDir + 2; + break; + case DOUBLE_LINE: + startPos1 = innDir; + startPos2 = (innDir + 4) % 8; + // startPos2 = (innDir + 4) >= 8 ? innDir - 4 : innDir + 4; + break; + default: + startPos2 = -1; + startPos1 = -1; + break; + } + return [startPos1, startPos2]; + } + + public static List rotateNekoResponse(int nekoDir) + { + var nekoDirVar = nekoDir; + if (nekoDir > 7) nekoDirVar = nekoDirVar - 8; + if (nekoDir < 0) nekoDirVar = nekoDirVar + 8; + // if (nekoDir > 7 | nekoDir < 0) return [-1, -1, -1, -1, -1, -1, -1, -1]; + + // List nekoResponse = [0, 1, -1, 2, 4, 6, -1, 7]; + List nekoResponse = new List(NekoOutputAtDir0); + + // 先增加方向 + for (int i = 0; i < nekoResponse.Count(); i++) + { + if (nekoResponse[i] == -1) continue; + nekoResponse[i] += nekoDirVar; + nekoResponse[i] = nekoResponse[i] % 8; + } + + // 根据输入的nekoDirVar,将NekoOutputAtDir0中的元素向右移动nekoDirVar次,最右边的回到index0的位置。 + int count = nekoResponse.Count(); + nekoResponse = new List(nekoResponse.Skip(count - nekoDirVar).Concat(nekoResponse.Take(count - nekoDirVar))); + + return nekoResponse; + } + + [ScriptMethod(name: "大转盘参数初始化(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31940|33000)$"], userControl: false)] + public void PurgationInit(Event @event, ScriptAccessory accessory) + { + // 初始化参数 + InnCircleFaceDir = [0, 0, 0]; // 内魔法阵面向 + InnCircleType = [0, 0, 0]; // 内魔法阵类型 + RotateCircleDir = [0, 0, 0]; // 大转盘旋转方向,-1逆时针,+1顺时针,0不转 + OuterFanMagic = [false, false, false, false, false, false, false, false]; // 外圈魔法阵分布,是扇形为True,是半场刀为False + OuterTargetMagicDir = [-1, -1]; // 大转盘目标 + accessory.Method.RemoveDraw(".*"); + + DebugMsg($"- 大转盘参数初始化。", accessory); + + } + + #endregion + + #region 炀火之咒 + [ScriptMethod(name: "炀火之咒初始危险区", eventType: EventTypeEnum.EnvControl, eventCondition: ["Index:00000004"])] + public async void Flamespire(Event @event, ScriptAccessory accessory) + { + var state = @event.State(); + bool isInterCard = false; + if (state == 0x00080004) + { + await Task.Delay(2500); + accessory.Method.RemoveDraw($"炀火之咒初始危险区"); + return; + } + if (IntercardFlags.Contains(state)) + { + DebugMsg($"检测到先四角安全", accessory); + isInterCard = true; + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"炀火之咒初始危险区"; + + if (!isInterCard) + { + dp.Position = new(100, 0, 100); + dp.Rotation = (float)Math.PI / 4; + dp.Scale = new(12, 40); + dp.Delay = 0; + dp.DestoryAt = 20000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + + dp.Rotation = (float)Math.PI / -4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + else + { + dp.Position = new(100, 0, 100); + dp.Rotation = 0; + dp.Scale = new(12, 40); + dp.Delay = 0; + dp.DestoryAt = 20000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + + dp.Rotation = (float)Math.PI / 2; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + } + #endregion + + public static void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + var msg = @event["Message"].ToString(); + accessory.Method.SendChat($"/e 获得玩家发送的消息:{msg}"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扇形死刑"; + dp.Scale = new(60); + dp.Position = new(100, 0, 100); + dp.TargetObject = accessory.Data.PartyList[0]; + dp.Radian = (float)Math.PI / 1.5f; + dp.Delay = 0; + dp.DestoryAt = 5000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } +} + +#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 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 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Index(this Event @event) + { + return ParseHexId(@event["Index"], out var id) ? id : 0; + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var id) ? id : 0; + } + + 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 id) + { + return sa.Data.Objects.SearchById(id); + } + +} + +public static class DirectionCalc +{ + + // 以北为0建立list + // InnGame List Dir + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Dir = Pi - InnGame (+ 2pi) + + public static float BaseInnGame2DirRad(float radian) + { + float r = (float)Math.PI - radian; + if (r < 0) r = (float)(r + 2 * Math.PI); + if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + public static float BaseDirRad2InnGame(float radian) + { + float r = (float)Math.PI - radian; + if (r < Math.PI) r = (float)(r + 2 * Math.PI); + if (r > Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + public static int DirRadRoundToDirs(float radian, int dirs) + { + var r = Math.Round(radian / (2f / dirs * Math.PI)); + if (r == dirs) r = r - dirs; + return (int)r; + } + + public static int PositionFloorToDirs(Vector3 point, Vector3 center, int dirs) + { + // 正分割,0°为分界线,将360°分为dirs份 + var r = Math.Floor(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + public static int PositionRoundToDirs(Vector3 point, Vector3 center, int dirs) + { + // 斜分割,0° return 0,将360°分为dirs份 + var r = Math.Round(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + public static float angle2Rad(float angle) + { + // 输入角度转为弧度 + float radian = (float)(angle * Math.PI / 180); + return radian; + } + + public static Vector3 RotatePoint(Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度,以北为0度 + // dp.Rotation是以南为0度,逆时针度数增加 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + } + + public static Vector3 ExtendPoint(Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + public static float FindRadian(Vector3 center, Vector3 new_point) + { + // 找到某点到中心的弧度 + float radian = MathF.PI - MathF.Atan2(new_point.X - center.X, new_point.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } +} + +public static class IndexHelper +{ + public static int getPlayerIdIndex(ScriptAccessory accessory, uint pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf(pid); + } + + public static int getMyIndex(ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + public static string getPlayerJobIndex(ScriptAccessory accessory, uint pid) + { + // 获得玩家职能简称 + var a = accessory.Data.PartyList.IndexOf(pid); + switch (a) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } +} + +public static class ColorHelper +{ + public static ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor colorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; +} + +#endregion diff --git a/lianying1997/06-EndWalker/RubicanteEx.cs b/lianying1997/06-EndWalker/RubicanteEx.cs new file mode 100644 index 0000000..d8ea551 --- /dev/null +++ b/lianying1997/06-EndWalker/RubicanteEx.cs @@ -0,0 +1,825 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using Lumina.Excel.Sheets; + +namespace UsamisScript.EndWalker.RubicanteEx; + +[ScriptType(name: "Rubicante-Ex [卢比坎特歼殛战]", territorys: [1096], + guid: "a5f70ab7-b79a-468c-9ffe-3c7e5091d71d", version: "0.0.0.4", author: "Usami", note: noteStr, updateInfo: UpdateInfo)] + +public class RubicanteEx +{ + const string noteStr = + """ + v0.0.0.4: + 很遗憾,大转盘必须Imgui。 + 鸭门。 + """; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public static bool DebugMode { get; set; } = false; + + const uint CLOCKWISE = 0x00020001; // EnvControl State 代表顺时针旋转 + const uint COUNTER = 0x00200010; // EnvControl State 代表逆时针旋转 + const uint INNER = 0x00000001; // EnvControl Index 代表内圈魔法阵 + const uint MIDDLE = 0x00000002; // EnvControl Index 代表中圈魔法阵 + const uint OUTER = 0x00000003; // EnvControl Index 代表外圈魔法阵 + const uint HALF_CLEAVE = 15760; // 外围魔法阵红色半场刀 SourceDataId + const uint FAN = 15759; // 外围魔法阵蓝色扇形 SourceDataId + const uint SINGLE_LINE = 542; // EnvControl Param 内圈魔法阵是单线 + const uint V_SHAPE = 543; // EnvControl Param 内圈魔法阵是V字 + const uint DOUBLE_LINE = 544; // EnvControl Param 内圈魔法阵是双线 + const uint NEKO = 545; // EnvControl Param 中圈魔法阵是猫猫 + const uint EIGHTPOS = 546; // EnvControl Param 外圈魔法阵是八方 + + List InnerCircleId = [null, null, null]; + List isCaptured = [false, false, false]; + List InnCircleFaceDir = [0, 0, 0]; // 内魔法阵面向 + List InnCircleType = [0, 0, 0]; // 内圈魔法阵类型 + List RotateCircleDir = [0, 0, 0]; // 大转盘旋转方向,-1逆时针,+1顺时针,0不转/未知方向 + List OuterFanMagic = [false, false, false, false, false, false, false, false]; // 外圈魔法阵分布,是扇形为True,是半场刀为False + static List NekoOutputAtDir0 = [0, 1, -1, 2, 4, 6, -1, 7]; // 当猫猫Dir为0时,输入与输出的响应 + List OuterTargetMagicDir = [-1, -1]; // 大转盘目标 + List IntercardFlags = [0x02000200, 0x00200020, 0x00020002, 0x00800080]; // 先斜角安全State标记 + public void Init(ScriptAccessory accessory) + { + InnerCircleId = [null, null, null]; + isCaptured = [false, false, false]; + + InnCircleFaceDir = [0, 0, 0]; // 内魔法阵面向 + InnCircleType = [0, 0, 0]; // 内魔法阵类型 + + RotateCircleDir = [0, 0, 0]; // 大转盘旋转方向,-1逆时针,+1顺时针,0不转 + OuterFanMagic = [false, false, false, false, false, false, false, false]; // 外圈魔法阵分布,是扇形为True,是半场刀为False + NekoOutputAtDir0 = [0, 1, -1, 2, 4, 6, -1, 7]; // 当猫猫Dir为0时,输入与输出的响应 + OuterTargetMagicDir = [-1, -1]; // 大转盘目标 + + IntercardFlags = [0x02000200, 0x00200020, 0x00020002, 0x00800080]; // 先斜角安全State标记 + + // DebugMsg($"Init Rubicante-Ex Success.", accessory); + + accessory.Method.MarkClear(); + accessory.Method.RemoveDraw(".*"); + } + + #region 大转盘 + [ScriptMethod(name: "炼狱魔阵外圈类型记录(不可控)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:regex:^(157(59|60))$"], userControl: false)] + public void MagicCircleOuterTypeRecord(Event @event, ScriptAccessory accessory) + { + // 15760 红 半场刀 + // 15759 蓝 扇形 + var srot = @event.SourceRotation(); + var sdid = @event.SourceDataId(); + var sdir = DirectionCalc.DirRadRoundToDirs(DirectionCalc.BaseInnGame2DirRad(srot), 8); + OuterFanMagic[sdir] = sdid == FAN ? true : false; + } + + [ScriptMethod(name: "炼狱魔阵内圈旋转方向记录(不可控)", eventType: EventTypeEnum.EnvControl, eventCondition: ["Index:regex:^(0000000[123])$"], userControl: false)] + public void MagicCircleRotationRecord(Event @event, ScriptAccessory accessory) + { + var idx = @event.Index(); + var rotate_dir = @event.State(); + int mcidx; + int mcdir; + string log = ""; + + // 定义 idx 和 rotate_dir 的映射关系 + var idxMapping = new Dictionary + { + { INNER, (0, "内环转动,") }, + { MIDDLE, (1, "中环转动,") }, + { OUTER, (2, "外环转动,") } + }; + + var dirMapping = new Dictionary + { + { COUNTER, (-1, "逆时针") }, + { CLOCKWISE, (1, "顺时针") } + }; + + if (idxMapping.ContainsKey(idx)) + { + var (_mcidx, _log) = idxMapping[idx]; + log = log + _log; + mcidx = _mcidx; + } + else + { + log = log + "未知环转动,"; + mcidx = 0; + } + + if (dirMapping.ContainsKey(rotate_dir)) + { + var (_mcdir, _log) = dirMapping[rotate_dir]; + log = log + _log; + mcdir = _mcdir; + } + else + { + log = log + "未知方向"; + mcdir = 0; + } + RotateCircleDir[mcidx] = mcdir; + DebugMsg(log, accessory); + } + + [ScriptMethod(name: "炼狱魔阵内圈类型与ID记录(不可控)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["Param:regex:^(54[23456])$"], userControl: false)] + public void MagicCircleTypeRecord(Event @event, ScriptAccessory accessory) + { + // 内为单线时,其面向与单线一致 + // 内为V字时,其面向与右侧(+2)方向有线 + // 内为双线时,其面向与后侧(+4)方向有线 + // 中为猫猫头时,其面向与猫耳朵相反 + // 外为八方,无人在意 + + var param = @event.Param(); + var tid = @event.TargetId(); + + var paramMapping = new Dictionary + { + { SINGLE_LINE, (0, "内圈", "单线") }, + { V_SHAPE, (0, "内圈", "V字") }, + { DOUBLE_LINE, (0, "内圈", "双线") }, + { NEKO, (1, "中圈", "猫猫")}, + { EIGHTPOS, (2, "外圈", "八方")} + }; + if (!paramMapping.ContainsKey(param)) return; + var (_idx, _pos, _type) = paramMapping[param]; + + // 记录ID + if (!isCaptured[_idx]) + { + isCaptured[_idx] = true; + InnerCircleId[_idx] = (IBattleChara?)accessory.GetById(tid); + DebugMsg($"确定{_pos}ID:{tid}", accessory); + } + + var rot = InnerCircleId[_idx]?.Rotation ?? 0f; + var logic_dir = DirectionCalc.DirRadRoundToDirs(DirectionCalc.BaseInnGame2DirRad((float)rot), 8); + InnCircleType[_idx] = param; + InnCircleFaceDir[_idx] = logic_dir; + DebugMsg($"检测到{_pos}魔法阵类型:{_type},逻辑方向{logic_dir}", accessory); + } + + [ScriptMethod(name: "大转盘算法", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33001"], userControl: false)] + public void PurgationAlgorithm(Event @event, ScriptAccessory accessory) + { + Task.Delay(100).ContinueWith(t => + { + // 基于内圈类型、内圈面向与内圈旋转方向获得起始点 + var startPos = getFireStartPos(InnCircleType[0], InnCircleFaceDir[0] + RotateCircleDir[0]); + string startPosStr = string.Join(", ", startPos); + DebugMsg($"- 检测到起始点:{startPosStr}", accessory); + + // 基于中圈猫猫的面向与旋转方向获得猫猫头响应 + List NekoResponse = rotateNekoResponse(InnCircleFaceDir[1] + RotateCircleDir[1]); + string NekoTarget = string.Join(", ", NekoResponse); + DebugMsg($"- 检测到猫猫头响应:{NekoTarget}", accessory); + + for (int i = 0; i < 2; i++) + { + if (startPos[i] != -1) + { + OuterTargetMagicDir[i] = NekoResponse[startPos[i]]; + } + } + string outerTarget = string.Join(", ", OuterTargetMagicDir); + DebugMsg($"- 检测到大转盘目标:{outerTarget}", accessory); + + // 根据转向,更改外圈魔法阵的目标 + int count = OuterFanMagic.Count(); + string RotateCircleDirStr = string.Join(", ", RotateCircleDir); + DebugMsg($"- 检测到大转盘转动数据:{RotateCircleDirStr}", accessory); + + var RotateParam = RotateCircleDir[2] == -1 ? 7 : RotateCircleDir[2]; + List OuterFanMagicBias = new List(OuterFanMagic.Skip(count - RotateParam).Concat(OuterFanMagic.Take(count - RotateParam))); + for (int i = 0; i < 2; i++) + { + if (OuterTargetMagicDir[i] != -1) + { + drawOuterMagic(OuterTargetMagicDir[i], OuterFanMagicBias[OuterTargetMagicDir[i]], accessory); + } + } + + }); + } + + public static void drawOuterMagic(int outDir, bool magicTypeIsFan, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + float radian = (float)Math.PI / 4 * outDir; + + dp.Name = $"外圈魔法阵{outDir}"; + dp.Position = DirectionCalc.RotatePoint(new(100, 0, 80), new(100, 0, 100), radian); + dp.TargetPosition = new(100, 0, 100); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 20000; + + dp.Scale = magicTypeIsFan ? new(45f) : new(45, 20); + dp.Radian = magicTypeIsFan ? (float)Math.PI / 3 : (float)Math.PI * 2; + accessory.Method.SendDraw(DrawModeEnum.Imgui, magicTypeIsFan ? DrawTypeEnum.Fan : DrawTypeEnum.Rect, dp); + } + + public static int[] getFireStartPos(uint innType, int innDir) + { + int startPos1; + int startPos2; + switch (innType) + { + case SINGLE_LINE: + startPos1 = innDir; + startPos2 = -1; + break; + case V_SHAPE: + startPos1 = innDir; + startPos2 = (innDir + 2) % 8; + // startPos2 = (innDir + 2) >= 8 ? innDir - 6 : innDir + 2; + break; + case DOUBLE_LINE: + startPos1 = innDir; + startPos2 = (innDir + 4) % 8; + // startPos2 = (innDir + 4) >= 8 ? innDir - 4 : innDir + 4; + break; + default: + startPos2 = -1; + startPos1 = -1; + break; + } + return [startPos1, startPos2]; + } + + public static List rotateNekoResponse(int nekoDir) + { + // if (nekoDir > 7 | nekoDir < 0) return [-1, -1, -1, -1, -1, -1, -1, -1]; + var nekoDirVar = nekoDir; + if (nekoDir > 7) nekoDirVar = nekoDirVar - 8; + if (nekoDir < 0) nekoDirVar = nekoDirVar + 8; + // List nekoResponse = [0, 1, -1, 2, 4, 6, -1, 7]; + List nekoResponse = new List(NekoOutputAtDir0); + + // 先增加方向 + for (int i = 0; i < nekoResponse.Count(); i++) + { + if (nekoResponse[i] == -1) continue; + nekoResponse[i] += nekoDirVar; + nekoResponse[i] = nekoResponse[i] % 8; + } + + // 根据输入的nekoDirVar,将NekoOutputAtDir0中的元素向右移动nekoDirVar次,最右边的回到index0的位置。 + int count = nekoResponse.Count(); + nekoResponse = new List(nekoResponse.Skip(count - nekoDirVar).Concat(nekoResponse.Take(count - nekoDirVar))); + + return nekoResponse; + } + + [ScriptMethod(name: "大转盘参数初始化(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:33001"], userControl: false)] + public void PurgationInit(Event @event, ScriptAccessory accessory) + { + // 初始化参数 + InnCircleFaceDir = [0, 0, 0]; // 内魔法阵面向 + InnCircleType = [0, 0, 0]; // 内魔法阵类型 + RotateCircleDir = [0, 0, 0]; // 大转盘旋转方向,-1逆时针,+1顺时针,0不转 + OuterFanMagic = [false, false, false, false, false, false, false, false]; // 外圈魔法阵分布,是扇形为True,是半场刀为False + OuterTargetMagicDir = [-1, -1]; // 大转盘目标 + accessory.Method.RemoveDraw(".*"); + + DebugMsg($"- 大转盘参数初始化。", accessory); + + } + + #endregion + #region 烈风火焰流 + + [ScriptMethod(name: "烈风火焰流:分散、双人分摊、四人分摊", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3200[234])$"])] + public void SpreadFlame(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var aid = @event.ActionId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"烈风火焰流{tid}"; + dp.Owner = tid; + dp.ScaleMode = ScaleMode.ByTime; + dp.Delay = 0; + dp.DestoryAt = 5000; + + var aidMapping = new Dictionary + { + { 32002, (5f, ColorHelper.colorPink.V4) }, // 单人分散 + { 32003, (6f, accessory.Data.DefaultSafeColor) }, // 四人分摊 + { 32004, (4f, accessory.Data.DefaultSafeColor) } // 二人分摊 + }; + + if (aidMapping.ContainsKey(aid)) + { + var properties = aidMapping[aid]; + dp.Scale = new(properties.scale); + dp.Color = properties.color; + } + + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "烈风火焰流:扇形分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31998"])] + public void FanSpread(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Owner = sid; + dp.Radian = float.Pi / 6; + dp.Scale = new(30); + dp.Delay = 0; + dp.DestoryAt = 7000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + dp.Name = $"放散火流{i}"; + dp.TargetObject = accessory.Data.PartyList[i]; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + + [ScriptMethod(name: "扇形死刑", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:00E6"], userControl: false)] + public void Dualfire(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扇形死刑{tid}"; + dp.Scale = new(60); + dp.Position = new(100, 0, 100); + dp.TargetObject = tid; + dp.Radian = (float)Math.PI / 1.5f; + dp.Delay = 0; + dp.DestoryAt = 5000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + #endregion + + #region 炀火之咒 + [ScriptMethod(name: "炀火之咒初始危险区", eventType: EventTypeEnum.EnvControl, eventCondition: ["Index:00000004"])] + public async void Flamespire(Event @event, ScriptAccessory accessory) + { + var state = @event.State(); + bool isInterCard = false; + if (state == 0x00080004) + { + await Task.Delay(2500); + accessory.Method.RemoveDraw($"炀火之咒初始危险区"); + return; + } + if (IntercardFlags.Contains(state)) + { + DebugMsg($"检测到先四角安全", accessory); + isInterCard = true; + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"炀火之咒初始危险区"; + + if (!isInterCard) + { + dp.Position = new(100, 0, 100); + dp.Rotation = (float)Math.PI / 4; + dp.Scale = new(12, 40); + dp.Delay = 0; + dp.DestoryAt = 20000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + + dp.Rotation = (float)Math.PI / -4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + else + { + dp.Position = new(100, 0, 100); + dp.Rotation = 0; + dp.Scale = new(12, 40); + dp.Delay = 0; + dp.DestoryAt = 20000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + + dp.Rotation = (float)Math.PI / 2; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + } + + [ScriptMethod(name: "炀火之咒Buff分散", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3485"])] + public void FlamespireSpreadBuff(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"炀火之咒Buff分散"; + dp.Owner = tid; + dp.Delay = 10000; + dp.DestoryAt = 5000; + dp.Scale = new(6); + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "火焰流圆形分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:32016"])] + public void InfernoSpread(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"火焰流圆形分散"; + dp.Owner = tid; + dp.DestoryAt = 5000; + dp.Scale = new(5); + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "环火烽火钢月分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3203[67])$"])] + public void ScaldingFleet(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"环烽火"; + dp.Owner = sid; + dp.Delay = 0; + dp.DestoryAt = 5000; + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + + if (@event.ActionId() == 32036) + { + // 烽火钢铁 + dp.Scale = new(10); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + else + { + // 环火月环 + dp.Scale = new(20); + dp.InnerScale = new(10); + dp.Radian = float.Pi * 2; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Owner = sid; + dp.Name = $"迅火预备{i}"; + dp.Scale = new(6, 40); + dp.TargetObject = accessory.Data.PartyList[i]; + dp.Delay = 0; + dp.DestoryAt = 6500; + dp.Color = ColorHelper.colorPink.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + + [ScriptMethod(name: "赤灭热波迅火回返", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:32039"])] + public void ImmolationChargeBack(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"迅火回返"; + dp.Owner = sid; + dp.Delay = 0; + dp.DestoryAt = 6500; + dp.Scale = new(6, 60); + dp.Color = ColorHelper.colorCyan.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + } + + [ScriptMethod(name: "赤灭热波分摊分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3203[45])$"])] + public void ImmolationSpreadStack(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"赤灭热波分摊分散"; + dp.Owner = tid; + dp.Delay = 0; + dp.DestoryAt = 7500; + + if (@event.ActionId() == 32035) + { + // 分摊 + dp.Scale = new(6); + dp.Color = accessory.Data.DefaultSafeColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + else + { + // 分散 + dp.Scale = new(5); + dp.Color = accessory.Data.DefaultDangerColor.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "赤灭热波半场刀", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3203[23])$"])] + public void ImmolationHalfCleave(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"赤灭热波半场刀"; + dp.Owner = sid; + dp.Delay = 0; + dp.DestoryAt = 7000; + dp.Scale = new(40, 20); + dp.Color = accessory.Data.DefaultDangerColor.WithW(3f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + #endregion + + public static void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + var msg = @event["Message"].ToString(); + accessory.Method.SendChat($"/e 获得玩家发送的消息:{msg}"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"扇形死刑"; + dp.Scale = new(60); + dp.Position = new(100, 0, 100); + dp.TargetObject = accessory.Data.PartyList[0]; + dp.Radian = (float)Math.PI / 1.5f; + dp.Delay = 0; + dp.DestoryAt = 5000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } +} + +#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 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 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Index(this Event @event) + { + return ParseHexId(@event["Index"], out var id) ? id : 0; + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var id) ? id : 0; + } + + 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 id) + { + return sa.Data.Objects.SearchById(id); + } +} + +public static class DirectionCalc +{ + + // 以北为0建立list + // InnGame List Dir + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Dir = Pi - InnGame (+ 2pi) + + public static float BaseInnGame2DirRad(float radian) + { + float r = (float)Math.PI - radian; + if (r < 0) r = (float)(r + 2 * Math.PI); + if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + public static float BaseDirRad2InnGame(float radian) + { + float r = (float)Math.PI - radian; + if (r < Math.PI) r = (float)(r + 2 * Math.PI); + if (r > Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + public static int DirRadRoundToDirs(float radian, int dirs) + { + var r = Math.Round(radian / (2f / dirs * Math.PI)); + if (r == dirs) r = r - dirs; + return (int)r; + } + + public static int PositionFloorToDirs(Vector3 point, Vector3 center, int dirs) + { + // 正分割,0°为分界线,将360°分为dirs份 + var r = Math.Floor(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + public static int PositionRoundToDirs(Vector3 point, Vector3 center, int dirs) + { + // 斜分割,0° return 0,将360°分为dirs份 + var r = Math.Round(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + public static float angle2Rad(float angle) + { + // 输入角度转为弧度 + float radian = (float)(angle * Math.PI / 180); + return radian; + } + + public static Vector3 RotatePoint(Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度,以北为0度 + // dp.Rotation是以南为0度,逆时针度数增加 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + } + + public static Vector3 ExtendPoint(Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + public static float FindRadian(Vector3 center, Vector3 new_point) + { + // 找到某点到中心的弧度 + float radian = MathF.PI - MathF.Atan2(new_point.X - center.X, new_point.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } +} + +public static class IndexHelper +{ + public static int getPlayerIdIndex(ScriptAccessory accessory, uint pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf(pid); + } + + public static int getMyIndex(ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + public static string getPlayerJobIndex(ScriptAccessory accessory, uint pid) + { + // 获得玩家职能简称 + var a = accessory.Data.PartyList.IndexOf(pid); + switch (a) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } +} + +public static class ColorHelper +{ + public static ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor colorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; +} + +#endregion diff --git a/lianying1997/06-EndWalker/TopReborn/TopReborn.cs b/lianying1997/06-EndWalker/TopReborn/TopReborn.cs new file mode 100644 index 0000000..4e3d1e4 --- /dev/null +++ b/lianying1997/06-EndWalker/TopReborn/TopReborn.cs @@ -0,0 +1,5729 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Lumina.Excel.Sheets; + +namespace UsamisKodakku.Scripts._06_EndWalker.TopReborn; + +[ScriptType(name: Name, territorys: [1122], guid: "6272e52b-5500-4ff0-8634-dd15f2e3b25e", + version: Version, author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class TopReborn +{ + const string NoteStr = + $""" + v{Version} + 修改自Karlin/Meva的脚本, + 谨慎使用特殊功能。 + """; + + const string UpdateInfo = + $""" + {Version} + 1. 适配 P5 二运在女人击退踩塔时标点的指挥逻辑,以解决 P5 二运后半不指路的问题。 + """; + + private const string Name = "绝欧精装 Reborn"; + private const string Version = "0.0.0.18"; + 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); + + private static PriorityDict _pd = new PriorityDict(); // 灵活多用字典 + private double _parse = 0; + + private static P1StateParams _p1 = new(); + private static P2StateParams _p2 = new(); + private static P3StateParams _p3 = new(); + private static P4StateParams _p4 = new(); + private static P5AStateParams _p5A = new(); + private static P5BStateParams _p5B = new(); + private static P5CStateParams _p5C = new(); + private static P6StateParams _p6 = new(); + + [UserSetting("启用方法设置中带*的特殊功能")] + public bool SpecialMode { get; set; } = false; + + public void Init(ScriptAccessory sa) + { + RefreshParams(sa); + sa.DebugMsg($"脚本 {Name} v{Version}{DebugVersion} 完成初始化", Debugging); + sa.Method.RemoveDraw(".*"); + sa.Method.ClearFrameworkUpdateAction(this); + } + + private void RefreshParams(ScriptAccessory sa) + { + _pd = new PriorityDict(); + _parse = 0; + ResetSupportUnitVisibility(sa); + + _p1.Reset(sa); + _p1.Dispose(); + _p2.Reset(sa); + _p2.Dispose(); + _p3.Reset(sa); + _p3.Dispose(); + _p4.Reset(sa); + _p4.Dispose(); + _p5A.Reset(sa); + _p5A.Dispose(); + _p5B.Reset(sa); + _p5B.Dispose(); + _p5C.Reset(sa); + _p5C.Dispose(); + _p6.Reset(sa); + _p6.Dispose(); + } + + private void ResetSupportUnitVisibility(ScriptAccessory sa) + { + const uint SUPPORTER_DATAID = 9020; + var objEnums = sa.GetByDataId(SUPPORTER_DATAID); + unsafe + { + foreach (var obj in objEnums) + { + sa.WriteVisible(obj, true); + } + } + } + + [ScriptMethod(name: "测试项:参数初始化", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 参数初始化(Event ev, ScriptAccessory sa) + { + RefreshParams(sa); + } + + [ScriptMethod(name: "测试项:展示优先级表格", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 展示优先级表格(Event ev, ScriptAccessory sa) + { + sa.DebugMsg(_pd.ShowPriorities(), Debugging); + } + + [ScriptMethod(name: "测试项:临时测试", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 临时测试(Event ev, ScriptAccessory sa) + { + var rot = 119.74815f.DegToRad(); + var startRegion = rot.RadianToRegion(12, 8, isDiagDiv: true); + var safeRegion = (startRegion + 3) % 12; // 某个安全区角度 + sa.DebugMsg($"炮区域:{startRegion},某个安全区区域:{safeRegion}", Debugging); + } + + #region P1A 循环程序 + + [ScriptMethod(name: "———————— 《P1A 循环程序》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P1A_循环程序_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P1A_循环程序_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31491"], userControl: Debugging)] + public void P1A_循环程序_分P(Event ev, ScriptAccessory sa) + { + _parse = 1.1; + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + _p1.BossId = ev.TargetId; + _p1.Register(); + _pd.Init(sa, "P1线塔"); + _pd.AddPriorities([2, 3, 1, 8, 4, 5, 6, 7]); // 数值越低,代表优先级越高 + } + + [ScriptMethod(name: "P1A_循环程序_Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3004|3005|3006|3451)$"], userControl: Debugging)] + public void P1A_循环程序_Buff记录(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + if (ev.SourceId == 0x00000000) return; + var idx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var priVal = ev.StatusId switch + { + 3004 => 10, + 3005 => 20, + 3006 => 30, + 3451 => 40, + _ => 0 + }; + _pd.AddPriority(idx, priVal); + } + + [ScriptMethod(name: "P1A_循环程序_塔收集", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["Operate:Add", "DataId:2013245"], userControl: Debugging)] + public void P1A_循环程序_塔收集(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + lock (_p1.塔字典) + { + // sa.DebugMsg($"准备塔收集 R{_p1.线塔轮次} {_p1.前一轮绘图清除完毕.WaitOne(0)}", Debugging); + + if (_p1.线塔轮次 != 0) + _p1.前一轮绘图清除完毕.WaitOne(); + + var towerPos = ev.SourcePosition; + var towerPriority = towerPos.GetRadian(Center).RadianToRegion(4, baseRegionIdx: 2, isDiagDiv: true, isCw: true); // 以北为0,顺时针增加 + _p1.塔字典[towerPriority] = towerPos; + // sa.DebugMsg($"在第 {_p1.线塔轮次} 轮收集到方位 {towerPriority} 的塔", Debugging); + if (_p1.塔字典.Count != 2) return; + _p1.线塔轮次++; + sa.DebugMsg($"线塔轮次增加 {_p1.线塔轮次} ", Debugging); + _p1.每轮塔收集完毕.Set(); + _p1.前一轮绘图清除完毕.Reset(); + } + } + + [ScriptMethod(name: "P1A_循环程序_集合提醒", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31491"], userControl: true)] + public void P1A_循环程序_集合提醒(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + sa.Method.TextInfo("Boss背后集合", 2000); + sa.Method.TTS("Boss背后集合"); + } + + [ScriptMethod(name: "P1A_循环程序_开始站位提醒", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3004|3005|3006|3451)$"])] + public void P1A_循环程序_开始站位提醒(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + if (ev.TargetId != sa.Data.Me) return; + + var isFirstTether = ev.StatusId == 3006; + sa.Method.TextInfo(isFirstTether ? "靠前接线" : "靠后", 3000); + sa.Method.TTS(isFirstTether ? "靠前接线" : "靠后"); + } + + [ScriptMethod(name: "P1A_循环程序_清理绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3149[67])$"], suppress: 100, userControl: Debugging)] + public void P1A_循环程序_清理绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + + sa.DebugMsg($"清除循环程序绘图 Round {_p1.线塔轮次}", Debugging); + + sa.Method.RemoveDraw($"P1_循环程序_R{_p1.线塔轮次}.*"); + _p1.塔字典 = new Dictionary(); + _p1.上一次靠近状态 = 0; + sa.Method.UnregistFrameworkUpdateAction(_p1.闲人指路Framework); + _p1.前一轮绘图清除完毕.Set(); + + if (_p1.线塔轮次 < 4) return; + sa.Method.UnregistFrameworkUpdateAction(_p1.扫描接线Framework); + } + + [ScriptMethod(name: "P1A_循环程序_线塔处理位置", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["Operate:Add", "DataId:2013245"], suppress: 500)] + public void P1A_循环程序_线塔处理位置(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + _p1.每轮塔收集完毕.WaitOne(); + int myIndex = sa.GetMyIndex(); + int myPriVal = _pd.Priorities[myIndex]; + int myTowerRound = myPriVal / 10; + int myLineRound = (myTowerRound - 1 + 2) % 4 + 1; + int myPriority = _pd.FindPriorityIndexOfKey(myIndex); // 升序排列,从0开始,偶数则优先级高,奇数则优先级低 + bool isHighPrior = myPriority % 2 == 0; + + try + { + if (_p1.线塔轮次 == myTowerRound) + { + var myTower = isHighPrior ? _p1.塔字典.MinBy(kvp => kvp.Key) : _p1.塔字典.MaxBy(kvp => kvp.Key); + // sa.DebugMsg($"现在是第 {_p1.线塔轮次} 轮线塔,是玩家的塔轮次,玩家需踩方位为 {myTower.Key} 的塔(以北为0,顺时针增加)", Debugging); + sa.DrawGuidance(myTower.Value, 0, 9000, $"P1_循环程序_R{_p1.线塔轮次}_塔站位"); + sa.DrawCircle(myTower.Value, 0, 9000, $"P1_循环程序_R{_p1.线塔轮次}_塔范围", 3f, isSafe: true); + } + else if (_p1.线塔轮次 == myLineRound) + { + var myLineRegion = isHighPrior ? 0 : 3; + while (_p1.塔字典.ContainsKey(myLineRegion)) + { + myLineRegion += isHighPrior ? 1 : -1; + if (myLineRegion is < 4 and > -1) continue; + sa.Log.Error($"没有找到玩家接线安全区"); + return; + } + var myLinePos = new Vector3(100, 0, 85).RotateAndExtend(Center, myLineRegion * -90f.DegToRad()); + // sa.DebugMsg($"现在是第 {_p1.线塔轮次} 轮线塔,是玩家的线轮次,玩家需将线接到方位 {myLineRegion} (以北为0,顺时针增加)", Debugging); + sa.DrawGuidance(myLinePos, 0, 9000, $"P1_循环程序_R{_p1.线塔轮次}_线站位"); + } + else + { + _p1.闲人指路Framework = sa.Method.RegistFrameworkUpdateAction(Action); + // sa.DebugMsg($"现在是第 {_p1.线塔轮次} 轮线塔,玩家闲人,在塔附近躲开即可(以北为0,顺时针增加)", Debugging); + + if (_p1.线塔轮次 < myTowerRound) + { + sa.DrawCircle(_p1.塔字典.First().Value, 0, 9000, $"P1_循环程序_R{_p1.线塔轮次}_塔1范围危险", 3f, isSafe: false); + sa.DrawCircle(_p1.塔字典.Last().Value, 0, 9000, $"P1_循环程序_R{_p1.线塔轮次}_塔2范围危险", 3f, isSafe: false); + } + + void Action() + { + Vector3 myPos = sa.Data.MyObject.Position; + Vector3 tower1Safe = new Vector3(100, 0, 86).RotateAndExtend(Center, _p1.塔字典.First().Key * -90f.DegToRad()); + Vector3 tower2Safe = new Vector3(100, 0, 86).RotateAndExtend(Center, _p1.塔字典.Last().Key * -90f.DegToRad()); + float distanceTower1 = Vector3.Distance(myPos, tower1Safe); + float distanceTower2 = Vector3.Distance(myPos, tower2Safe); + int currentStatus = distanceTower1 < distanceTower2 ? 1 : 2; + + // 如果状态发生变化,更新绘制 + if (currentStatus == _p1.上一次靠近状态) return; + + // 移除旧的绘制 + sa.Method.RemoveDraw($"P1_循环程序_R{_p1.线塔轮次}_闲站位{_p1.上一次靠近状态}"); + sa.DrawGuidance(currentStatus == 1 ? tower1Safe : tower2Safe, 0, Int32.MaxValue, $"P1_循环程序_R{_p1.线塔轮次}_闲站位{currentStatus}"); + _p1.上一次靠近状态 = currentStatus; + } + } + } + finally + { + _p1.每轮塔收集完毕.Reset(); + } + } + + [ScriptMethod(name: "P1A_循环程序_接线标记", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31496)$", "TargetIndex:1"], suppress: 500)] + public void P1A_循环程序_接线标记(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + _p1.每轮塔收集完毕.WaitOne(1000); + + int myIndex = sa.GetMyIndex(); + int myPriVal = _pd.Priorities[myIndex]; + int myTowerRound = myPriVal / 10; + int myLineRound = (myTowerRound - 1 + 2) % 4 + 1; + + if (_p1.线塔轮次 != myLineRound) return; + + int myPriority = _pd.FindPriorityIndexOfKey(myIndex); // 升序排列,从0开始,偶数则优先级高,奇数则优先级低 + int targetPriority = (myPriority - 2 + 8) % 8; // 需要接线玩家的优先级Idx + var targetPartyIndex = _pd.SelectSpecificPriorityIndex(targetPriority).Key; + + var dp = sa.DrawLine(ev.TargetId, sa.Data.PartyList[targetPartyIndex], 0, 9000, + $"P1_循环程序_R{_p1.线塔轮次}_接线标记", 0, 5, 10, byY: true, isSafe: true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp); + + // sa.DebugMsg($"玩家优先级升序序列为 {myPriority},需接序列为 {targetPriority} {sa.GetPlayerJobByIndex(targetPartyIndex)} 的线", Debugging); + } + + [ScriptMethod(name: "P1A_循环程序_接线标记移除", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0059"], userControl: Debugging)] + public void P1A_循环程序_接线标记移除(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + if (ev.SourceId != sa.Data.Me) return; + sa.Method.RemoveDraw($"P1_循环程序_R{_p1.线塔轮次}_接线标记"); + } + + [ScriptMethod(name: "P1A_循环程序_接线玩家大圈绘图", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0059"], userControl: true)] + public void P1A_循环程序_接线玩家大圈绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 1.1) return; + + if (_p1.扫描接线开启) return; + _p1.扫描接线开启 = true; + + // 当且仅当本轮正确的玩家接线,且其与Boss有一段距离,才会绘制大圈 + _p1.扫描接线Framework = sa.Method.RegistFrameworkUpdateAction(Action); + + const uint TETHER_ID = 0x59; + return; + + void Action() + { + var bossObj = sa.GetById(_p1.BossId); + if (bossObj is null) return; + + foreach (var member in sa.Data.PartyList) + { + // 找到小队成员 + IGameObject? memberObj = sa.GetById(member); + if (memberObj is null) continue; + int memberIdx = sa.GetPlayerIdIndex(member); + + void CleanUp() + { + _p1.接线绘图字典.Remove(memberIdx, out _); + sa.Method.RemoveDraw($"P1_循环程序_R{_p1.线塔轮次}_线源{memberIdx}"); + } + + if (!_pd.Priorities.TryGetValue(memberIdx, out int memberPrival) || + memberPrival < 10) { CleanUp(); continue; } // 优先级未准备好 + + // 计算线塔轮次 + int memberTowerRound = memberPrival / 10; + int memberLineRound = (memberTowerRound - 1 + 2) % 4 + 1; + if (memberLineRound != _p1.线塔轮次) { CleanUp(); continue; } + + // 距离判断 + float distance = Vector3.Distance(memberObj.Position, bossObj.Position); + if (distance < 5f) { CleanUp(); continue; } + + // 线源追溯 + var tetherSource = sa.GetTetherSource((IBattleChara?)memberObj, TETHER_ID); + bool isCorrectTether = tetherSource.Count == 1 && tetherSource[0] == _p1.BossId; + if (!isCorrectTether) { CleanUp(); continue; } + + // 避免反复绘图 + if (_p1.接线绘图字典.TryAdd(memberIdx, true)) + sa.DrawCircle(member, 0, Int32.MaxValue, $"P1_循环程序_R{_p1.线塔轮次}_线源{memberIdx}", 15); + + } + } + } + + #endregion P1A 循环程序 + + #region P1B 全能之主 + + [ScriptMethod(name: "———————— 《P1B 全能之主》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P1B_全能之主_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P1B_全能之主_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31499"], userControl: Debugging)] + public void P1B_全能之主_分P(Event ev, ScriptAccessory sa) + { + _parse = 1.2; + _pd.Init(sa, "P1全能之主"); + _pd.AddPriorities([2, 3, 1, 8, 4, 5, 6, 7]); // 数值越低,代表优先级越高 + _p1.全能之主轮次 = 1; + } + + [ScriptMethod(name: "P1B_全能之主_Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3004|3005|3006|3451)$"], userControl: Debugging)] + public void P1B_全能之主_Buff记录(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + if (ev.SourceId == 0x00000000) return; + var idx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var priVal = ev.StatusId switch + { + 3004 => 10, + 3005 => 20, + 3006 => 30, + 3451 => 40, + _ => 0 + }; + _pd.AddPriority(idx, priVal); + } + + [ScriptMethod(name: "P1B_全能之主_顺逆时针记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31501|32368)$"], userControl: Debugging)] + public void P1B_全能之主_顺逆时针记录(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + if (_p1.全能之主顺逆时针判断完毕) return; + + const uint FIRST_FLAME = 31501; + const uint REST_FLAME = 32368; + + lock (_p1) + { + if (ev.ActionId == FIRST_FLAME && _p1.全能之主第一次角度寄存 < -8) + { + _p1.全能之主第一次角度寄存 = ev.SourceRotation; + sa.DebugMsg($"得到全能之主第一次角度寄存 {_p1.全能之主第一次角度寄存.RadToDeg()}", Debugging); + _p1.全能之主第一次角度寄存完毕事件.Set(); + } + + if (ev.ActionId == REST_FLAME) + { + float diff = ev.SourceRotation.GetDiffRad(_p1.全能之主第一次角度寄存); + sa.DebugMsg($"计算当前角度{ev.SourceRotation.RadToDeg()} 与 前一次的差为 {diff.RadToDeg()}", Debugging); + if (MathF.Abs(diff) > float.Pi / 2) return; + _p1.全能之主为顺时针 = diff < 0; + _p1.全能之主顺逆时针判断完毕 = true; + sa.DebugMsg($"全能之主方向判断完毕,{(diff < 0 ? "顺" : "逆")}时针", Debugging); + _p1.全能之主顺逆时针判断完毕事件.Set(); + } + } + } + + [ScriptMethod(name: "P1B_全能之主_起跑线", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31501)$"], userControl: true)] + public void P1B_全能之主_起跑线(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + if (_p1.全能之主起跑线绘制完毕) return; + _p1.全能之主起跑线绘制完毕 = true; + + int myIndex = sa.GetMyIndex(); + int myPriority = _pd.FindPriorityIndexOfKey(myIndex); // 升序排列,从0开始,偶数则优先级高,奇数则优先级低 + bool isHighPrior = myPriority % 2 == 0; + + _p1.全能之主第一次角度寄存完毕事件.WaitOne(); + var startRegion = _p1.全能之主第一次角度寄存.RadianToRegion(12, 8, isDiagDiv: true); + startRegion = (startRegion + 3) % 12; // 某个安全区角度 + sa.DebugMsg($"某个安全区角度是:{startRegion},我是高优先级:{isHighPrior},我排第 {myPriority}", Debugging); + var isNotMyRegion = startRegion < 6 ^ isHighPrior; + if (isNotMyRegion) + startRegion = (startRegion + 6) % 12; + sa.DebugMsg($"最终决定的安全区角度:{startRegion}", Debugging); + var bossObj = sa.GetById(_p1.BossId); + if (bossObj is null) return; + var bossPos = bossObj.Position; + + var startRad = ((startRegion + 12 - 8) % 12 * 30f).DegToRad(); + // 绘制一条从boss本体出发到tempRad的线 + var dp = sa.DrawLine(bossPos, 0, 0, 6000, $"P1_全能之主_起跑线", startRad, 1f, 20f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + _p1.全能之主顺逆时针判断完毕事件.WaitOne(); + var routeRad = startRad + (_p1.全能之主为顺时针 ? -float.Pi / 2 : float.Pi / 2); + // 绘制顺逆时针表示 + for (int i = 0; i < 4; i++) + { + var basePoint = bossPos + new Vector3(0, 0, 1); + var startPoint = basePoint.RotateAndExtend(bossPos, startRad, (i + 1) * 4.5f); + var dp0 = sa.DrawLine(startPoint, 0, 0, 6000, $"P1_全能之主_起跑指针", routeRad, 1f, 2f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp0); + } + + _p1.全能之主第一次角度寄存完毕事件.Reset(); + _p1.全能之主顺逆时针判断完毕事件.Reset(); + } + + [ScriptMethod(name: "P1B_全能之主_轮次增加", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31502"], userControl: Debugging, suppress: 500)] + public void P1B_全能之主_轮次增加(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + sa.Method.RemoveDraw($"P1_全能之主_R{_p1.全能之主轮次}.*"); + _p1.全能之主轮次++; + if (_p1.全能之主轮次 > 4) return; + sa.DebugMsg($"现在是 全能之主 第{_p1.全能之主轮次}轮", Debugging); + } + + [ScriptMethod(name: "P1B_全能之主_出去提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(349[567]|3424)$"])] + public void P1B_全能之主_出去提示(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + + if (ev.TargetId != sa.Data.Me) return; + _p1.全能之主出去时间Framework = sa.Method.RegistFrameworkUpdateAction(Action); + return; + + void Action() + { + var statusId = ev.StatusId; + var dur = sa.GetStatusRemainingTime(sa.Data.MyObject, statusId); + if (dur > 5f) return; + sa.Method.TextInfo("出去出去", 2000); + sa.Method.TTS("出去出去"); + sa.Method.UnregistFrameworkUpdateAction(_p1.全能之主出去时间Framework); + } + } + + [ScriptMethod(name: "P1B_全能之主_回头提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31502", "TargetIndex:1"])] + public void P1B_全能之主_回头提示(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + if (ev.TargetId != sa.Data.Me) return; + sa.Method.TextInfo("回头", 2000); + sa.Method.TTS("回头"); + } + + [ScriptMethod(name: "P1B_全能之主_点名直线", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0017"])] + public void P1B_全能之主_点名直线(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + if (sa.Data.MyObject.IsTank()) return; // 坦克无需关注 + bool isMe = ev.TargetId == sa.Data.Me; + sa.DrawRect(_p1.BossId, ev.TargetId, 0, 5000, $"P1_全能之主_点名直线", 0, 6, 50, isMe); + } + + [ScriptMethod(name: "P1B_全能之主_后半指路", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0017"], suppress: 15000)] + public void P1B_全能之主_后半指路(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + var bossObj = sa.GetById(_p1.BossId); + if (bossObj is null) return; + var bossPos = bossObj.Position; + + List rotDeg = [180, 180, -54, 54, -18, 18, -90, 90]; + int myIndex = sa.GetMyIndex(); + var pos = (bossPos + new Vector3(0, 0, 10)).RotateAndExtend(bossPos, rotDeg[myIndex].DegToRad(), + sa.Data.MyObject.IsTank() ? 2.5f : 0); + sa.DrawGuidance(pos, 0, 6000, $"P1_全能之主_后半指路"); + + for (int i = 0; i < 8; i++) + { + var dp = sa.DrawLine(bossPos, 0, 0, 6000, $"P1_全能之主_指引线", rotDeg[i].DegToRad(), 20f, 20f, draw: false); + dp.Color = i switch + { + 0 or 1 => new Vector4(0.1f, 0.1f, 1, 1), + 2 or 3 => new Vector4(0.1f, 1f, 0.1f, 1), + _ => new Vector4(1, 0.1f, 0.1f, 1), + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + } + + [ScriptMethod(name: "P1B_全能之主_最远顺劈范围", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0017"], suppress: 15000)] + public void P1B_全能之主_最远顺劈范围(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + + var bossObj = sa.GetById(_p1.BossId); + if (bossObj is null) return; + _p1.全能之主最远距离Framework = sa.Method.RegistFrameworkUpdateAction(Action); + + void Action() + { + // 1. 建立距离字典 + var bossPos = bossObj.Position; + var distanceDict = new Dictionary(sa.Data.PartyList.Count); + foreach (var memberId in sa.Data.PartyList) + { + var member = sa.GetById(memberId); + if (member == null || !member.IsValid()) continue; + distanceDict[sa.GetPlayerIdIndex(memberId)] = Vector3.Distance(member.Position, bossPos); + } + + // 2. 只取前两名 + var topTwo = distanceDict.OrderByDescending(kvp => kvp.Value) + .Take(2) + .Select(kvp => kvp.Key) + .ToArray(); + + // 3. 模式判断 + int myIndex = sa.GetMyIndex(); + bool isInTopTwo = topTwo.Contains(myIndex); + bool isTank = sa.Data.MyObject.IsTank(); + + const int IS_TANK_NOT_TOP2_DANGER = 1; + const int NOT_TANK_IS_TOP2_DANGER = 2; + const int NOT_TANK_NOT_TOP2_SAFE = 3; + const int IS_TANK_IS_TOP2_SAFE = -1; + + int currentState = (isTank, isInTopTwo) switch + { + (true, false) => IS_TANK_NOT_TOP2_DANGER, + (false, true) => NOT_TANK_IS_TOP2_DANGER, + (false, false) => NOT_TANK_NOT_TOP2_SAFE, + _ => IS_TANK_IS_TOP2_SAFE + }; + + // 4. 状态未改变直接返回 + if (currentState == _p1.上一次远距离状态) return; + + // 5. 是坦克且在最远,不绘图,直接返回且清除绘制 + if (currentState == IS_TANK_IS_TOP2_SAFE) + { + sa.Method.RemoveDraw("P1_全能之主后半_顺劈状态.*"); + return; + } + + var isDangerous = currentState != NOT_TANK_NOT_TOP2_SAFE; + var radian = (isDangerous ? 30f : 120f).DegToRad(); + var color = isDangerous ? new Vector4(1f, 0.1f, 0.1f, 2f) : sa.Data.DefaultSafeColor; + + // 绘制逻辑 + if (currentState == NOT_TANK_IS_TOP2_DANGER) + { + DrawFanCleave(myIndex, radian, color, !isDangerous, currentState); + } + else if (currentState == IS_TANK_NOT_TOP2_DANGER) + { + if (topTwo[0] > 1) DrawFanCleave(topTwo[0], radian, color, !isDangerous, currentState); + if (topTwo[1] > 1) DrawFanCleave(topTwo[1], radian, color, !isDangerous, currentState); + } + else + { + // 最远是谁,与我何干,直接画T + DrawFanCleave(0, radian, color, !isDangerous, currentState); + DrawFanCleave(1, radian, color, !isDangerous, currentState); + } + + _p1.上一次远距离状态 = currentState; + return; + + void DrawFanCleave(int playerIdx, float radian, Vector4 color, bool isSafe, int state) + { + var dp = sa.DrawFan(_p1.BossId, sa.Data.PartyList[playerIdx], 0, int.MaxValue, + $"P1_全能之主后半_顺劈状态{state}", radian, 0, 20, 0, isSafe: isSafe, draw: false); + dp.Color = color; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + + switch (state) + { + case IS_TANK_NOT_TOP2_DANGER: + sa.Method.RemoveDraw($"P1_全能之主后半_顺劈状态{NOT_TANK_IS_TOP2_DANGER}"); + sa.Method.RemoveDraw($"P1_全能之主后半_顺劈状态{NOT_TANK_NOT_TOP2_SAFE}"); + break; + case NOT_TANK_IS_TOP2_DANGER: + sa.Method.RemoveDraw($"P1_全能之主后半_顺劈状态{IS_TANK_NOT_TOP2_DANGER}"); + sa.Method.RemoveDraw($"P1_全能之主后半_顺劈状态{NOT_TANK_NOT_TOP2_SAFE}"); + break; + case NOT_TANK_NOT_TOP2_SAFE: + sa.Method.RemoveDraw($"P1_全能之主后半_顺劈状态{IS_TANK_NOT_TOP2_DANGER}"); + sa.Method.RemoveDraw($"P1_全能之主后半_顺劈状态{NOT_TANK_IS_TOP2_DANGER}"); + break; + } + } + } + } + + [ScriptMethod(name: "P1B_全能之主_扩散波动炮计数", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31504"], suppress: 200, userControl: Debugging)] + public void P1B_全能之主_扩散波动炮计数(Event ev, ScriptAccessory sa) + { + if (_parse != 1.2) return; + _p1.扩散波动炮次数++; + + if (_p1.扩散波动炮次数 == 5) + { + sa.Method.UnregistFrameworkUpdateAction(_p1.全能之主最远距离Framework); + sa.Method.RemoveDraw($"P1.*"); + } + } + + #endregion P1B 全能之主 + + #region P2 欧米茄防火墙设置 + + [ScriptMethod(name: "———————— 《P2 欧米茄防火墙设置》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2_欧米茄防火墙设置_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2A_防火墙_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31552"], userControl: Debugging)] + public void P2A_防火墙_分P(Event ev, ScriptAccessory sa) + { + _parse = 2; + sa.Method.RemoveDraw($"P1.*"); + _p1.Reset(sa); + _p1.Dispose(); + _p2.Register(); + } + + [ScriptMethod(name: "P2A_防火墙_BossId记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3155[23])$"], userControl: Debugging)] + public void P2A_防火墙_BossId记录(Event ev, ScriptAccessory sa) + { + const uint FIREWALL_MALE = 31552; + const uint FIREWALL_FEMALE = 31553; + + switch (ev.ActionId) + { + case FIREWALL_FEMALE: + _p2.BossIdFemale = ev.SourceId; + break; + case FIREWALL_MALE: + _p2.BossIdMale = ev.SourceId; + break; + default: + return; + } + } + + [ScriptMethod(name: "*P2A_防火墙_屏蔽无效目标", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31552"], userControl: true)] + public void P2A_防火墙_屏蔽无效目标(Event ev, ScriptAccessory sa) + { + if (_parse != 2) return; + + if (!SpecialMode) return; + _p2.使能防火墙 = true; + _p2.判断防火墙目标Framework = sa.Method.RegistFrameworkUpdateAction(Action); + const uint MALE_DISABLE = 3499; + const uint FEMALE_DISABLE = 3500; + return; + + void Action() + { + var myObject = sa.Data.MyObject; + if (myObject is null) return; + + if (!_p2.使能防火墙) + { + if (_p2.上一次防火墙状态 == 0) return; + // P2一运期间 + sa.SetTargetable(sa.GetById(_p2.BossIdMale), false); + sa.SetTargetable(sa.GetById(_p2.BossIdFemale), false); + _p2.上一次防火墙状态 = 0; + return; + } + + const int MALE_UNTARGETABLE = 1; + const int FEMALE_UNTARGETABLE = 2; + const int FREELY_TARGETABLE = 3; + const int UNREACHABLE = 4; + + int currentState = (myObject.HasStatus(MALE_DISABLE), myObject.HasStatus(FEMALE_DISABLE)) switch + { + (true, false) => MALE_UNTARGETABLE, + (false, true) => FEMALE_UNTARGETABLE, + (false, false) => FREELY_TARGETABLE, + _ => UNREACHABLE + }; + + if (currentState == _p2.上一次防火墙状态 && _p2.上一次防火墙状态 != 0) return; + + _p2.上一次防火墙状态 = currentState; + sa.SetTargetable(sa.GetById(_p2.BossIdMale), !myObject.HasStatus(MALE_DISABLE)); + sa.SetTargetable(sa.GetById(_p2.BossIdFemale), !myObject.HasStatus(FEMALE_DISABLE)); + } + } + + [ScriptMethod(name: "P2A_防火墙_一运期间暂时关闭", eventType: EventTypeEnum.Targetable, + eventCondition: ["Targetable:False", "DataId:regex:^(1571[23])$"], userControl: Debugging, suppress: 1000)] + public void P2A_防火墙_一运期间暂时关闭(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + sa.DebugMsg($"P2A_防火墙_一运期间暂时关闭", Debugging); + _p2.使能防火墙 = false; + } + + [ScriptMethod(name: "P2A_防火墙_男女人性别交换", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3151[78])$"], userControl: Debugging, suppress: 1000)] + public void P2A_防火墙_男女人性别交换(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + sa.DebugMsg($"P2A_防火墙_男女人性别交换", Debugging); + (_p2.BossIdFemale, _p2.BossIdMale) = (_p2.BossIdMale, _p2.BossIdFemale); + } + + [ScriptMethod(name: "*P2A_防火墙_一运后再次开启", eventType: EventTypeEnum.Targetable, + eventCondition: ["Targetable:True", "DataId:regex:^(1571[23])$"], userControl: Debugging)] + public void P2A_防火墙_一运后再次开启(Event ev, ScriptAccessory sa) + { + if (_parse != 2.15) return; + if (!SpecialMode) return; + lock (_p2) + { + _p2.可选目标数量++; + sa.DebugMsg($"P2A_防火墙_一运后再次开启:{_p2.可选目标数量}", Debugging); + if (_p2.可选目标数量 < 2) return; + sa.DebugMsg($"P2A_防火墙_一运后再次开启", Debugging); + _p2.使能防火墙 = true; + } + } + + [ScriptMethod(name: "P2A_防火墙_二运前关闭防火墙判断", eventType: EventTypeEnum.StatusRemove, + eventCondition: ["StatusID:regex:^(3500|3499)$"], userControl: Debugging, suppress: 1000)] + public void P2A_防火墙_二运前关闭防火墙判断(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + sa.Method.UnregistFrameworkUpdateAction(_p2.判断防火墙目标Framework); + _p2.使能防火墙 = false; + sa.SetTargetable(sa.GetById(_p2.BossIdMale), true); + sa.SetTargetable(sa.GetById(_p2.BossIdFemale), true); + } + + [ScriptMethod(name: "*P2A_防火墙_无敌设置不可选中", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(671)$"], userControl: true)] + public void P2_防火墙_无敌设置不可选中(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + if (!SpecialMode) return; + sa.SetTargetable(sa.GetById(ev.TargetId), false); + } + + [ScriptMethod(name: "P2A_防火墙_无敌移除", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:regex:^(671)$"], userControl: Debugging)] + public void P2_防火墙_无敌移除(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + sa.SetTargetable(sa.GetById(ev.TargetId), true); + } + + #endregion P2 欧米茄防火墙设置 + + #region P2A 协作程序 + + [ScriptMethod(name: "———————— 《P2A 协作程序》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2A_协作程序_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2A_协作程序_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31550"], userControl: Debugging)] + public void P2A_协作程序_分P(Event ev, ScriptAccessory sa) + { + _parse = 2.1; + _pd.Init(sa, "P2索尼"); + _pd.AddPriorities([2, 3, 1, 8, 4, 5, 6, 7]); // 数值越低,代表优先级越高 + } + + [ScriptMethod(name: "P2A_协作程序_远近记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3427|3428)$"], userControl: Debugging, suppress: 10000)] + public void P2_协作程序_远近记录(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + const uint MID_GLITCH = 3427, REMOTE_GLITCH = 3428; + _p2.协作程序是远线 = ev.StatusId == REMOTE_GLITCH; + sa.DebugMsg($"记录下协作程序是 {(_p2.协作程序是远线 ? "远" : "近")} 线", Debugging); + } + + [ScriptMethod(name: "P2A_协作程序_索尼记录", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(01A[0123])$"], userControl: Debugging)] + public void P2_协作程序_索尼记录(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + const uint CIRCLE_1 = 416, CROSS_2 = 419, TRIANGLE_3 = 417, SQUARE_4 = 418; + lock (_pd) + { + var idx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var priVal = ev.Id0() switch + { + CIRCLE_1 => 10, + CROSS_2 => 20, + TRIANGLE_3 => 30, + SQUARE_4 => 40, + _ => 0 + }; + _pd.AddPriority(idx, priVal); + _pd.AddActionCount(); + if (_pd.ActionCount != 8) return; + + // 为排在右边的四个人加上100优先值 + var (key1, key2, key3, key4) = (_pd.SelectSpecificPriorityIndex(1).Key, + _pd.SelectSpecificPriorityIndex(3).Key, + _pd.SelectSpecificPriorityIndex(5).Key, _pd.SelectSpecificPriorityIndex(7).Key); + + _pd.AddPriority(key1, 100); + _pd.AddPriority(key2, 100); + _pd.AddPriority(key3, 100); + _pd.AddPriority(key4, 100); + } + + } + + [ScriptMethod(name: "P2A_协作程序_男女人攻击范围", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7747", "SourceDataId:regex:^(1571[45])$"])] + public void P2_协作程序_男女人攻击范围(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + Vector3 pos = ev.SourcePosition; + if (Vector3.Distance(pos, Center) > 12) return; + + var obj = sa.GetById(ev.SourceId); + if (obj == null) return; + + var dataId = obj.DataId; + var transId = sa.GetTransformationId(obj); + if (transId == null) return; + + const uint MAN = 15714, WOMAN = 15715; + // const byte MAN_CHARIOT = 0, MAN_DONUT = 4, WOMAN_CROSS = 0, WOMAN_HOTWING = 4; + + switch (dataId == MAN, transId == 0) + { + case (true, true): + sa.DrawCircle(ev.SourceId, 0, 5500, $"P2_协作程序_男女人攻击范围_男钢铁", 10); + break; + case (true, false): + sa.DrawDonut(ev.SourceId, 0, 5500, $"P2_协作程序_男女人攻击范围_男月环", 40, 10); + break; + case (false, true): + var dp1 = sa.DrawRect(ev.SourceId, 0, 5500, $"P2_协作程序_男女人攻击范围_女十字1", 0, 10, 60, draw: false); + var dp2 = sa.DrawRect(ev.SourceId, 0, 5500, $"P2_协作程序_男女人攻击范围_女十字2", float.Pi / 2, 10, 60, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp2); + break; + case (false, false): + var dp3 = sa.DrawDonut(ev.SourceId, 0, 5500, $"P2_协作程序_男女人攻击范围_女辣翅", 60, 8, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.HotWing, dp3); + break; + } + } + + [ScriptMethod(name: "P2A_协作程序_男女人攻击范围删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3152[56])$"], userControl: Debugging, suppress: 10000)] + public void P2_协作程序_男女人攻击范围删除(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + // 采用男人的钢铁月环释放标记删除 + sa.Method.RemoveDraw($"P2_协作程序_男女人攻击范围.*"); + _p2.眼睛激光准备绘图.Set(); + } + + [ScriptMethod(name: "P2A_协作程序_眼睛激光范围", eventType: EventTypeEnum.EnvControl, eventCondition: ["Flag:2"])] + public void P2_协作程序_眼睛激光范围(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + _p2.眼睛激光准备绘图.WaitOne(); + var basePos = new Vector3(100, 0, 80); + var eyePos = basePos.RotateAndExtend(Center, -45f.DegToRad() * _p2.大眼睛方位); + sa.DrawRect(eyePos, Center, 0, 10000, "P2_协作程序_眼睛激光", 0, 16, 40); + _p2.眼睛激光准备绘图.Reset(); + } + + [ScriptMethod(name: "P2A_协作程序_眼睛激光与索尼指路删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31521"], userControl: Debugging)] + public void P2_协作程序_眼睛激光与索尼指路删除(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + sa.Method.RemoveDraw($"P2_协作程序_眼睛激光"); + sa.Method.RemoveDraw($"P2_协作程序_索尼站位"); + } + + [ScriptMethod(name: "P2A_协作程序_眼睛激光方位记录", eventType: EventTypeEnum.EnvControl, eventCondition: ["Flag:2"], userControl: Debugging)] + public void P2_协作程序_眼睛激光方位记录(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + var region = ev.Index() - 1; + sa.DebugMsg($"记录下Flag2,region:{region}", Debugging); + // Index从1开始,以A为初始,顺时针增加 + _p2.大眼睛方位 = (int)region; + _p2.眼睛激光方位记录.Set(); + } + + [ScriptMethod(name: "P2A_协作程序_索尼站位", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3152[56])$"], + userControl: true, suppress: 10000)] + public void P2_协作程序_索尼站位(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + _p2.眼睛激光方位记录.WaitOne(); + + // BasePos的定义均以 圆高-叉高-角高-方高-圆低-...的顺序 + List middleBasePos = + [ + new(88.5f, 0, 85.5f), new(88.5f, 0, 95.0f), new(88.5f, 0, 105.0f), new(88.5f, 0, 114.5f), + new(111.5f, 0, 85.5f), new(111.5f, 0, 95.5f), new(111.5f, 0, 105.0f), new(111.5f, 0, 114.5f) + ]; + List farBasePos = + [ + new(91.5f, 0, 83.0f), new(82.0f, 0, 93.0f), new(82.0f, 0, 107.0f), new(91.5f, 0, 117.0f), + new(108.5f, 0, 117.0f), new(118.0f, 0, 107.0f), new(118.0f, 0, 93.0f), new(108.5f, 0, 83.0f) + ]; + + sa.DebugMsg(_pd.ShowPriorities(), Debugging); + var rank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + var pos = (_p2.协作程序是远线 ? farBasePos : middleBasePos)[rank].RotateAndExtend(Center, _p2.大眼睛方位 * -45f.DegToRad()); + sa.DrawGuidance(pos, 0, 10000, $"P2_协作程序_索尼站位"); + + _p2.眼睛激光方位记录.Reset(); + } + + [ScriptMethod(name: "P2A_协作程序_男人钢铁位置记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31516"], userControl: Debugging)] + public void P2_协作程序_男人钢铁位置记录(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + _p2.男人钢铁方位 = ev.SourcePosition.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + sa.DebugMsg($"男人钢铁方位 {_p2.男人钢铁方位}", Debugging); + _p2.男人钢铁方位记录.Set(); + } + + [ScriptMethod(name: "P2A_协作程序_分摊记录", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0064"], userControl: Debugging)] + public void P2_协作程序_分摊站位(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + lock (_pd) + { + var index = sa.GetPlayerIdIndex((uint)ev.TargetId); + _pd.AddPriority(index, 1000); + + // 找到连线搭档,增加优先值 + var icon = _pd.Priorities[index].GetDecimalDigit(2); + foreach (var kvp in _pd.Priorities) + { + if (kvp.Key == index) continue; + if (kvp.Value.GetDecimalDigit(2) != icon) continue; + _pd.AddPriority(kvp.Key, 500); + } + + _pd.AddActionCount(); + if (_pd.ActionCount < 10) return; + _p2.分摊记录.Set(); + sa.DebugMsg(_pd.ShowPriorities(), Debugging); + _parse = 2.15; + sa.DebugMsg($"阶段转为{_parse}", Debugging); + } + } + + [ScriptMethod(name: "P2A_协作程序_分摊指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31521"], userControl: true)] + public void P2_协作程序_分摊指路(Event ev, ScriptAccessory sa) + { + if (_parse != 2.1) return; + + _p2.分摊记录.WaitOne(); + _p2.男人钢铁方位记录.WaitOne(); + + try + { + int leftRegion = (_p2.男人钢铁方位 + 2 + 8) % 8; + int rightRegion = (_p2.男人钢铁方位 - (_p2.协作程序是远线 ? 2 : 4) + 8) % 8; + sa.DebugMsg($"分摊左方位 {leftRegion},右方位 {rightRegion}"); + + var dp1 = sa.DrawLine(Center, 0, 0, 6000, $"P2_协作程序_指引线左", leftRegion * 45f.DegToRad(), 20f, 20f, + draw: false); + dp1.Color = new Vector4(0.1f, 1f, 0.1f, 1); + var dp2 = sa.DrawLine(Center, 0, 0, 6000, $"P2_协作程序_指引线右", rightRegion * 45f.DegToRad(), 20f, 20f, + draw: false); + dp2.Color = new Vector4(0.1f, 1f, 0.1f, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp2); + + // 左:男人钢铁方位记录+2;右:-2(远)或-4(中) + const int IDLE = 1; + const int STACK_PARTNER = 2; + const int STACK_SOURCE = 3; + + int myPriVal = _pd.Priorities[sa.GetMyIndex()]; + int myRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + + (int myState, string myStateStr) = myPriVal switch + { + > 1000 => (STACK_SOURCE, "分摊源"), + > 500 => (STACK_PARTNER, "分摊搭档"), + _ => (IDLE, "闲人") + }; + + int safePosRegion = 0; + bool bothStackSourceAtRight = _pd.SelectSpecificPriorityIndex(1, true).Value.GetDecimalDigit(3) == 1; + bool bothStackSourceAtLeft = _pd.SelectSpecificPriorityIndex(0, true).Value.GetDecimalDigit(3) == 0; + + switch (myState) + { + case IDLE: + { + safePosRegion = myPriVal.GetDecimalDigit(3) == 0 ? leftRegion : rightRegion; + // safePosRegion = myRank <= 1 ? leftRegion : rightRegion; + break; + } + case STACK_PARTNER: + { + var needReverse = bothStackSourceAtLeft || (bothStackSourceAtRight && _p2.协作程序是远线); + safePosRegion = myRank == (needReverse ? 5 : 4) ? leftRegion : rightRegion; + break; + } + case STACK_SOURCE: + { + var needReverse = bothStackSourceAtRight && !_p2.协作程序是远线; + safePosRegion = myRank == (needReverse ? 7 : 6) ? leftRegion : rightRegion; + break; + } + } + sa.DebugMsg($"玩家类型为 {myStateStr},安全区在 {(safePosRegion == leftRegion ? "左" : "右")}", Debugging); + var pos = new Vector3(100, 0, 105).RotateAndExtend(Center, safePosRegion * 45f.DegToRad()); + sa.DrawGuidance(pos, 0, 10000, $"P2_协作程序_分摊击退位置"); + + _p2.女人击退记录.WaitOne(); + + sa.Method.RemoveDraw($"P2_协作程序_分摊击退位置"); + var pos2 = new Vector3(100, 0, _p2.协作程序是远线 ? 119.5f : 115f).RotateAndExtend(Center, safePosRegion * 45f.DegToRad()); + sa.DrawGuidance(pos2, 0, 10000, $"P2_协作程序_分摊击退后续"); + + _p2.男人分摊钢铁记录.WaitOne(); + sa.Method.RemoveDraw($"P2_协作程序.*"); + } + finally + { + _p2.分摊记录.Reset(); + _p2.男人钢铁方位记录.Reset(); + _p2.女人击退记录.Reset(); + _p2.男人分摊钢铁记录.Reset(); + } + } + + [ScriptMethod(name: "P2A_协作程序_女人场中击退记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31534"], + userControl: Debugging, suppress: 10000)] + public void P2_协作程序_女人击退记录(Event ev, ScriptAccessory sa) + { + if (_parse != 2.15) return; + _p2.女人击退记录.Set(); + } + + [ScriptMethod(name: "P2A_协作程序_男人分摊钢铁记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31526"], + userControl: Debugging, suppress: 10000)] + public void P2_协作程序_男人分摊钢铁记录(Event ev, ScriptAccessory sa) + { + if (_parse != 2.15) return; + _p2.男人分摊钢铁记录.Set(); + } + + #endregion P2A 协作程序 + + #region P2B 刀光剑舞 + + [ScriptMethod(name: "———————— 《P2B 刀光剑舞》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P2B_刀光剑舞_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2B_刀光剑舞_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31544"], userControl: Debugging)] + public void P2_刀光剑舞_分P(Event @event, ScriptAccessory accessory) + { + _parse = 2.2; + } + + [ScriptMethod(name: "P2B_刀光剑舞_射手天箭", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31539"], userControl: true)] + public void P2_刀光剑舞_射手天剑(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + sa.DrawRect(ev.SourceId, 1000, 7000, $"P2_刀光剑舞_射手天剑", 0, 10, 42); + } + + [ScriptMethod(name: "P2B_刀光剑舞_射手天箭删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31539"], userControl: Debugging)] + public void P2_刀光剑舞_射手天箭删除(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + sa.Method.RemoveDraw($"P2_刀光剑舞_射手天箭"); + } + + [ScriptMethod(name: "P2B_刀光剑舞_接线顺劈", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3154[01])$"], userControl: true)] + public void P2_刀光剑舞_接线顺劈(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + var dp = sa.DrawFan(ev.SourceId, 0, 5500, 2000, $"P2_刀光剑舞_接线顺劈", + float.Pi / 2, 0, 40, 0, draw: false); + dp.TargetResolvePattern = PositionResolvePatternEnum.TetherTarget; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "P2B_刀光剑舞_接线顺劈删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3154[01])$"], userControl: Debugging, suppress: 10000)] + public void P2_刀光剑舞_接线顺劈删除(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + sa.Method.RemoveDraw($"P2_刀光剑舞_接线顺劈"); + } + + [ScriptMethod(name: "P2B_刀光剑舞_盾连击指路方向提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31527"], userControl: true)] + public void P2_刀光剑舞_盾连击指路方向提示(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + if (sa.Data.MyObject.IsTank()) + { + var dp = sa.DrawLine(sa.Data.Me, ev.SourceId, 0, 10000, $"P2_刀光剑舞_盾连击男人连线", 0, 3f, 10f, isSafe: true, byY: true, draw: false); + dp.Color = new Vector4(0f, 1f, 1f, 1f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp); + + var spos = ev.SourcePosition; + var myPos = new Vector3(100, 0, 108).RotateAndExtend(Center, + spos.GetRadian(Center) + (sa.GetMyIndex() == 0 ? 20f : -20f).DegToRad()); + sa.DrawGuidance(myPos, 0, 10000, $"P2_刀光剑舞_盾连击指路方向提示"); + } + else + sa.DrawGuidance(Center, 0, 10000, $"P2_刀光剑舞_盾连击指路方向提示"); + } + + [ScriptMethod(name: "P2B_刀光剑舞_盾连击指路方向删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31527"], userControl: Debugging)] + public void P2_刀光剑舞_盾连击指路方向删除(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + sa.Method.RemoveDraw($"P2_刀光剑舞_盾连击.*"); + } + + [ScriptMethod(name: "P2B_刀光剑舞_盾连击后分摊", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31528", "TargetIndex:1"], userControl: true)] + public void P2_刀光剑舞_盾连击后分摊(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + + var targetIsMe = ev.TargetId == sa.Data.Me; + var dp = sa.DrawCircle(ev.SourceId, 0, 10000, $"P2_刀光剑舞_分摊", 6f, isSafe: !targetIsMe, draw: false); + dp.SetOwnersDistanceOrder(true, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + sa.Method.TextInfo(targetIsMe ? "出去出去" : "集合分摊", 3000); + sa.Method.TTS(targetIsMe ? "出去出去" : "集合分摊"); + } + + [ScriptMethod(name: "P2B_刀光剑舞_盾连击后分摊删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31529", "TargetIndex:1"], userControl: Debugging)] + public void P2_刀光剑舞_盾连击后分摊删除(Event ev, ScriptAccessory sa) + { + if (_parse != 2.2) return; + sa.Method.RemoveDraw($"P2_刀光剑舞_分摊"); + } + + #endregion P2B 刀光剑舞 + + #region P2C 转场 + + [ScriptMethod(name: "P2C_转场_分P", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31507"], userControl: Debugging)] + public void P2C_转场_分P(Event ev, ScriptAccessory sa) + { + _parse = 2.5; + _pd.Init(sa, "P2.5转场"); + } + + [ScriptMethod(name: "P2C_转场_记录头标", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[1234679]|10)$"], + userControl: Debugging)] + public void P2C_转场_记录头标(Event ev, ScriptAccessory sa) + { + // 取攻击1234、锁链12、禁止12 + if (_parse != 2.5) return; + + lock (_pd) + { + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var targetJob = sa.GetPlayerJobByIndex(tidx); + sa.DebugMsg($"检测到{targetJob} 被标 ev.Id {mark}", Debugging); + _pd.AddActionCount(); + var pdVal = mark switch + { + 0x01 => 10, // 攻击1 + 0x02 => 20, // 攻击2 + 0x03 => 30, // 攻击3 + 0x04 => 40, // 攻击4 + 0x06 => 100, // 锁链1 + 0x07 => 110, // 锁链2 + 0x09 => 200, // 禁止1 + 0x10 => 210, // 禁止2 + _ => 0 + }; + _pd.AddPriority(tidx, pdVal); + if (_pd.ActionCount != 8) return; + sa.DebugMsg($"P2C_转场_记录头标:头标记录完毕", Debugging); + sa.DebugMsg($"{_pd.ShowPriorities()}", Debugging); + _p2.转场头标记录.Set(); // 头标记录 + } + } + + [ScriptMethod(name: "P2C_转场_初始位置指路", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3426)$"], + userControl: true, suppress: 10000)] + public void P2C_转场_初始位置(Event ev, ScriptAccessory sa) + { + if (_parse != 2.5) return; + + // Atk1-4, Bind1-2, Stop1-2 + List deg = [-90f, -30f, 30f, 90f, -150f, 150f, -150f, 150f]; + int myIndex = sa.GetMyIndex(); + + var hasMarker = _p2.转场头标记录.WaitOne(3000); + + if (!hasMarker) + { + sa.DebugMsg($"未在一定时间内检测到头标,启用THD优先级排列", Debugging); + _pd.AddPriorities([1, 2, 3, 4, 5, 6, 7, 8]); + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var obj = sa.GetById(sa.Data.PartyList[i]); + if (obj is null) return; + (bool hasStack, bool hasSpread) = (((IBattleChara)obj).HasStatus(3426), ((IBattleChara)obj).HasStatus(3425)); + var priVal = (hasStack, hasSpread) switch + { + (true, false) => 100, + (false, true) => 10, + (false, false) => 200, + _ => 0 + }; + if (priVal == 0) return; + _pd.AddPriority(i, priVal); + } + } + + float myPosDeg = deg[_pd.FindPriorityIndexOfKey(myIndex)]; + var pos = new Vector3(100, 0, 119.5f).RotateAndExtend(Center, myPosDeg.DegToRad()); + sa.DrawGuidance(pos, 0, 5000, $"P2C_转场_初始位置指路"); + _p2.转场头标记录.Reset(); + } + + [ScriptMethod(name: "P2C_转场_地震", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3156[789]|31570)$", "TargetIndex:1"], + userControl: true)] + public void P2C_转场_地震(Event ev, ScriptAccessory sa) + { + if (_parse != 2.5) return; + const uint CHARIOT = 31567, DONUT_1 = 31568, DONUT_2 = 31569, DONUT_3 = 31570; + + (int outScale, int innerScale, int donutCount) = ev.ActionId switch + { + CHARIOT => (12, 6, 1), + DONUT_1 => (18, 12, 2), + DONUT_2 => (24, 18, 3), + _ => (0, 0, 4) + }; + + string prefix = "P2C_转场_地震月环"; + sa.Method.RemoveDraw($"{prefix}{donutCount-1}"); + if (donutCount == 4) return; + + var dp = sa.DrawDonut(Center, 0, 10000, $"{prefix}{donutCount}", outScale, innerScale, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(2f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + + if (donutCount != 3) return; + sa.Method.RemoveDraw($"P2C_转场_初始位置指路"); + } + + [ScriptMethod(name: "P2C_转场_手臂位置记录", eventType: EventTypeEnum.PlayActionTimeline, + eventCondition: ["Id:regex:^(774[78])$", "SourceDataId:regex:^(1571[89])$"], userControl: Debugging, suppress: 10000)] + public void P2C_转场_手臂位置记录(Event ev, ScriptAccessory sa) + { + if (_parse != 2.5) return; + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(6, isDiagDiv: true); + _p2.转场手臂先正三角 = region % 2 == 1; + } + + [ScriptMethod(name: "P2C_转场_分摊分散指路", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(31566)$"], userControl: true, suppress: 10000)] + public void P2C_转场_分摊分散指路(Event ev, ScriptAccessory sa) + { + if (_parse != 2.5) return; + + // Atk1-4, Bind1-2, Stop1-2 + List deg = [-90f, -30f, 30f, 90f, -150f, 150f, -150f, 150f]; + int myPriIndex = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + + float myPosDeg = deg[myPriIndex]; + Vector3 regionPos = new Vector3(100, 0, 119.5f).RotateAndExtend(Center, myPosDeg.DegToRad()); + + List ccwPrep = [false, true, false, true, true, false, true, false]; + List ccwMovement = [true, false, true, false, false, true, false, true]; + + const float PREP_ROTATE_DEG_BASE = 7.5f; + const float MOVEMENT_ROTATE_DEG_BASE = 25f; + const float MOVEMENT_EXTEND_DISTANCE_BASE = -3f; + + float prepRotateDeg = PREP_ROTATE_DEG_BASE.DegToRad() * (ccwPrep[myPriIndex] ? 1 : -1) * (_p2.转场手臂先正三角 ? 1 : -1); + float movementRotateDeg = MOVEMENT_ROTATE_DEG_BASE.DegToRad() * (ccwMovement[myPriIndex] ? 1 : -1) * (_p2.转场手臂先正三角 ? 1 : -1); + + Vector3 prepPos = regionPos.RotateAndExtend(Center, prepRotateDeg); + Vector3 movementPos = prepPos.RotateAndExtend(Center, movementRotateDeg, MOVEMENT_EXTEND_DISTANCE_BASE); + + sa.DrawGuidance(prepPos, 0, 2000, $"P2C_转场_分摊分散指路1"); + sa.DrawGuidance(prepPos, movementPos, 0, 2000, $"P2C_转场_分摊分散指路2预备", isSafe: false); + sa.DrawGuidance(movementPos, 2000, 2000, $"P2C_转场_分摊分散指路2"); + } + + [ScriptMethod(name: "*P2C_转场_屏蔽场地月环与手臂钢铁释放后特效", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3156[689]|31570)$", "TargetIndex:1"], userControl: true)] + public void P2C_转场_屏蔽特效(Event ev, ScriptAccessory sa) + { + if (_parse != 2.5) return; + if (!SpecialMode) return; + sa.WriteVisible(sa.GetById((uint)ev.SourceId), false); + } + + #endregion P2C 转场 + + #region P3A 你好世界 + + [ScriptMethod(name: "———————— 《P3A 你好世界》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3A_你好世界_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A_你好世界_初始八方", eventType: EventTypeEnum.Targetable, eventCondition: ["DataId:15717"], userControl: true)] + public void P3A_你好世界_初始八方(Event ev, ScriptAccessory sa) + { + if (_parse != 2.5) return; + List region = [4, 2, 6, 0, 7, 1, 5, 3]; + sa.DrawGuidance(new Vector3(100, 0, 105f).RotateAndExtend(Center, region[sa.GetMyIndex()] * 45f.DegToRad()), 0, + 5000, $"P3A_你好世界_初始八方"); + } + + [ScriptMethod(name: "P3A_你好世界_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31573"], userControl: Debugging)] + public void P3A_你好世界_分P(Event ev, ScriptAccessory sa) + { + _parse = 3; + _p2.Reset(sa); + _p2.Dispose(); + _p3.Register(); + _pd.Init(sa, "P3HW"); + _p3.BossId = ev.SourceId; + + ResetSupportUnitVisibility(sa); + } + + [ScriptMethod(name: "P3A_你好世界_初始八方删除", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31573"], userControl: Debugging)] + public void P3A_你好世界_初始八方删除(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + sa.Method.RemoveDraw($"P3A_你好世界_初始八方"); + } + + [ScriptMethod(name: "P3A_你好世界_Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(343[6789]|3527)$"], + userControl: Debugging)] + public void P3A_你好世界_Buff记录(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + if (ev.SourceId == 0x00000000) return; + const uint STACK_PREP = 3436; + const uint DEFAMATION_PREP = 3437; + const uint RED_ROT_PREP = 3438; + const uint BLUE_ROT_PREP = 3439; + const uint NEED_DEFAMATION = 3527; + + lock (_pd) + { + if (_pd.ActionCount >= 10) return; + var score = ev.StatusId switch + { + BLUE_ROT_PREP => 2, + RED_ROT_PREP => 4, + STACK_PREP => 1, + DEFAMATION_PREP => 16, + NEED_DEFAMATION => 8, + _ => 0 + }; + _pd.AddPriority(sa.GetPlayerIdIndex((uint)ev.TargetId), score); + _pd.AddActionCount(); + if (_pd.SelectSpecificPriorityIndex(0, true).Value != 20) return; + _p3.大圈是红塔 = true; + + } + } + + [ScriptMethod(name: "P3A_你好世界_轮数增加", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31599)$"], + userControl: Debugging)] + public void P3A_你好世界_轮数增加(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + _p3.你好世界轮数++; + _p3.红塔位置 = 0; + _p3.蓝塔位置 = 0; + sa.DebugMsg($"现在是 你好世界 第{_p3.你好世界轮数}轮", Debugging); + sa.DebugMsg(_pd.ShowPriorities(), Debugging); + _p3.你好世界轮数记录.Set(); + } + + [ScriptMethod(name: "P3A_你好世界_红蓝塔方位记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3158[34])$"], + userControl: Debugging)] + public void P3A_你好世界_红蓝塔方位记录(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + + _p3.你好世界轮数记录.WaitOne(); + + const uint RED_TOWER = 31583; + const uint BLUE_TOWER = 31584; + + lock (_pd) + { + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + if (ev.ActionId == RED_TOWER) + AddTowerParam(ref _p3.红塔位置, region); + else + AddTowerParam(ref _p3.蓝塔位置, region); + if (_p3.红塔位置 / 100 + _p3.蓝塔位置 / 100 < 4) return; + _p3.红蓝塔方位记录.Set(); + } + void AddTowerParam(ref int towerParam, int region) + { + towerParam += region * (towerParam > 0 ? 1 : 10); + towerParam += 100; + } + } + + [ScriptMethod(name: "P3A_你好世界_初始目的地标注", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31599)$"], + userControl: true)] + public void P3A_你好世界_初始目的地标注(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + + // 分摊后拉远 -> 分摊 -> 大圈后靠近 -> 大圈 -> + const int STATE_REMOTE_BREAK = 0; + const int STATE_STACK = 1; + const int STATE_LOCAL_BREAK = 2; + const int STATE_DEFAMATION = 3; + + _p3.你好世界轮数记录.WaitOne(); + _p3.红蓝塔方位记录.WaitOne(); + + const float REMOTE_OUT = 11.5f; + const float STACK_OUT = 12f; + const float DEFAMATION_OUT = 15f; + const float LOCAL_OUT = 14f; + + const float REMOTE_CLOSE_DEG = 30f; + const float STACK_CLOSE_DEG = 20f; + const float DEFAMATION_CLOSE_DEG = 0f; + const float LOCAL_CLOSE_DEG = -30f; + + try + { + // 计算玩家当前状态(0:远线, 1:分摊, 2:近线, 3:大圈) + var myState = (_pd.FindPriorityIndexOfKey(sa.GetMyIndex()) / 2 + _p3.你好世界轮数 - 1 + 4) % 4; + sa.DebugMsg($"玩家当前状态:{myState},0分摊远线,1分摊塔,2大圈近线,3大圈塔", Debugging); + sa.DebugMsg($"红塔位置:{_p3.红塔位置},蓝塔位置:{_p3.蓝塔位置},大圈是红塔 {_p3.大圈是红塔}", Debugging); + + int defamationTower = _p3.大圈是红塔 ? _p3.红塔位置 : _p3.蓝塔位置; + int stackTower = _p3.大圈是红塔 ? _p3.蓝塔位置 : _p3.红塔位置; + var (towerPos, baseAngle, extend, prefix) = GetStateConfig(myState, defamationTower, stackTower); + DrawDestinationMarks(sa, towerPos, baseAngle, extend, prefix, _p3.你好世界轮数); + sa.Method.TextInfo(prefix, 5000); + } + finally + { + _p3.红蓝塔方位记录.Reset(); + } + + return; + + (int towerPos, float baseAngle, float extend, string prefix) GetStateConfig( + int state, int defamationTower, int stackTower) + { + return state switch + { + STATE_DEFAMATION => (defamationTower, DEFAMATION_CLOSE_DEG, DEFAMATION_OUT, $"大圈【{(_p3.大圈是红塔 ? "红" : "蓝")}】塔内"), + STATE_REMOTE_BREAK => (stackTower, REMOTE_CLOSE_DEG, REMOTE_OUT, $"远线【{(_p3.大圈是红塔 ? "蓝" : "红")}】塔间分摊"), + STATE_STACK => (stackTower, STACK_CLOSE_DEG, STACK_OUT, $"分摊【{(_p3.大圈是红塔 ? "蓝" : "红")}】塔内"), + STATE_LOCAL_BREAK => _p3.你好世界轮数 == 4 + ? (stackTower, REMOTE_CLOSE_DEG, REMOTE_OUT, $"近线第四轮【{(_p3.大圈是红塔 ? "蓝" : "红")}】塔间分摊") // 第4轮的近线使用远线 + : (defamationTower, LOCAL_CLOSE_DEG, LOCAL_OUT, $"近线【{(_p3.大圈是红塔 ? "红" : "蓝")}】塔外分散"), // 其他轮次使用大圈塔 + _ => throw new ArgumentException($"未知状态: {state}") + }; + } + + void DrawDestinationMarks(ScriptAccessory sa, int towerPos, float baseAngle, + float extend, string prefix, int round) + { + // 判断第一个塔是否记录在个位(true:个位, false:十位) + // bool isFirstAtDigitOne = towerPos.GetDecimalDigit(1) < towerPos.GetDecimalDigit(2); + // 两者之差为±2或±6,当相减结果为-2或6时,个位数代表的塔方位为第一枚,逆时针转动2后为第二枚 + bool isFirstAtDigitOne = Math.Abs(2 - (towerPos.GetDecimalDigit(1) - towerPos.GetDecimalDigit(2))) == 4; + float direction = isFirstAtDigitOne ? 1f : -1f; + + for (int i = 1; i <= 2; i++) + { + float offsetAngle = baseAngle * direction * (i == 1 ? 1f : -1f); + int region = towerPos.GetDecimalDigit(i); + + Vector3 basePos = new Vector3(100, 0, 100 + extend); + float rotateRad = (region * 45f + offsetAngle).DegToRad(); + Vector3 pos = basePos.RotateAndExtend(Center, rotateRad); + + sa.DebugMsg($"绘制 第{round}轮 {prefix}", Debugging); + var dp = sa.DrawCircle(pos, 0, 10000, $"P3A_你好世界_初始目的地标注_R{round}_{prefix}{i}", 0.5f, isSafe: true, draw: false); + dp.Color = sa.Data.DefaultSafeColor.WithW(2f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + } + } + + [ScriptMethod(name: "P3A_你好世界_大圈与近线传毒标注", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3158[34])$", "TargetIndex:1"], + userControl: true, suppress: 500)] + public void P3A_你好世界_大圈与近线传毒标注(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + + // 分摊后拉远 -> 分摊 -> 大圈后靠近 -> 大圈 -> + const int STATE_REMOTE_BREAK = 0; + const int STATE_STACK = 1; + const int STATE_LOCAL_BREAK = 2; + const int STATE_DEFAMATION = 3; + + // 计算玩家当前状态(0:远线, 1:分摊, 2:近线, 3:大圈) + var myState = (_pd.FindPriorityIndexOfKey(sa.GetMyIndex()) / 2 + _p3.你好世界轮数 - 1 + 4) % 4; + var myPos = sa.Data.MyObject.Position; + + // 大圈标注八方点 + if (myState == STATE_DEFAMATION) + { + var targetPos = GetDefamationPosition(); + sa.DrawGuidance(targetPos, 0, 5000, $"P3A_你好世界_大圈标注_R{_p3.你好世界轮数}_{myState}"); + return; + } + + // 近线标注大圈传毒 + if (myState == STATE_LOCAL_BREAK) + { + if (_p3.你好世界轮数 == 4) return; + var target = GetDefamationPlayer(sa); + sa.DrawGuidance(target, 0, 5000, $"P3A_你好世界_近线标注_R{_p3.你好世界轮数}_{myState}"); + } + + // 本地方法:获取大圈应去的塔位置 + Vector3 GetDefamationPosition() + { + var towerRegion = _p3.大圈是红塔 ? _p3.红塔位置 : _p3.蓝塔位置; + var towerRegion1 = towerRegion.GetDecimalDigit(1); + var towerRegion2 = towerRegion.GetDecimalDigit(2); + + var pos1 = new Vector3(100, 0, 114).RotateAndExtend(Center, towerRegion1 * 45f.DegToRad()); + var pos2 = new Vector3(100, 0, 114).RotateAndExtend(Center, towerRegion2 * 45f.DegToRad()); + + return Vector3.Distance(myPos, pos1) < Vector3.Distance(myPos, pos2) ? pos1 : pos2; + } + + uint GetDefamationPlayer(ScriptAccessory sa) + { + var defamationPlayers = 0; + for (int i = 0; i < 8; i++) + { + var playerState = (_pd.FindPriorityIndexOfKey(i) / 2 + _p3.你好世界轮数 - 1 + 4) % 4; + if (playerState != STATE_DEFAMATION) continue; + defamationPlayers += 100 + (defamationPlayers > 0 ? i * 10 : i); + if (defamationPlayers > 200) break; + } + + var player1 = sa.Data.PartyList[defamationPlayers.GetDecimalDigit(1)]; + var player2 = sa.Data.PartyList[defamationPlayers.GetDecimalDigit(2)]; + + var pos1 = sa.GetById(player1).Position; + var pos2 = sa.GetById(player2).Position; + + return Vector3.Distance(myPos, pos1) < Vector3.Distance(myPos, pos2) ? player1 : player2; + } + } + + [ScriptMethod(name: "P3A_你好世界_轮数记录状态重置", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3158[34])$", "TargetIndex:1"], + userControl: Debugging, suppress: 500)] + public void P3A_你好世界_轮数记录状态重置(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + sa.Method.RemoveDraw($"P3A_你好世界_初始目的地标注_R{_p3.你好世界轮数}.*"); + _p3.你好世界轮数记录.Reset(); + } + + [ScriptMethod(name: "P3A_你好世界_大圈与近线传毒标注删除", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3526|3429)$"], userControl: Debugging)] + public void P3A_你好世界_大圈与近线传毒标注删除(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + + // 当且仅当自身周围发身传毒事件才删除 + var myPos = sa.Data.MyObject.Position; + var targetPos = sa.GetById(ev.TargetId).Position; + if (Vector3.Distance(myPos, targetPos) > 5f) return; + + sa.Method.RemoveDraw($"P3A_你好世界_近线标注_R{_p3.你好世界轮数}.*"); + sa.Method.RemoveDraw($"P3A_你好世界_大圈标注_R{_p3.你好世界轮数}.*"); + } + + [ScriptMethod(name: "P3A_你好世界_小钢铁", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3435|3528)$"], userControl: true)] + public void P3A_你好世界_小钢铁(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + sa.DrawCircle(ev.TargetId, 2500, 10000, $"P3A_你好世界_小钢铁_{ev.TargetId}", 5f); + } + + [ScriptMethod(name: "P3A_你好世界_小钢铁删除", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:regex:^(3435|3528)$"], userControl: Debugging)] + public void P3A_你好世界_小钢铁删除(Event ev, ScriptAccessory sa) + { + if (_parse != 3) return; + sa.Method.RemoveDraw($"P3A_你好世界_小钢铁_{ev.TargetId}"); + } + + #endregion P3A 你好世界 + + #region P3B 小电视 + + [ScriptMethod(name: "P3B_小电视_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31588"], userControl: Debugging)] + public void P3B_转场_分P(Event ev, ScriptAccessory sa) + { + // 使用严重错误读条提前进入小电视 + _parse = 3.1; + _pd.Init(sa, "P3B小电视"); + _pd.AddPriorities([2, 3, 1, 7, 4, 5, 6, 7]); + } + + [ScriptMethod(name: "P3B_小电视_记录头标", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[12345678])$"], + userControl: Debugging)] + public void P3B_小电视_记录头标(Event ev, ScriptAccessory sa) + { + // 取攻击12345、锁链123 + if (_parse != 3.1) return; + + lock (_pd) + { + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var targetJob = sa.GetPlayerJobByIndex(tidx); + sa.DebugMsg($"检测到{targetJob} 被标 ev.Id {mark}", Debugging); + _pd.AddActionCount(); + var pdVal = mark switch + { + 0x01 => 10, // 攻击1 + 0x02 => 20, // 攻击2 + 0x03 => 30, // 攻击3 + 0x04 => 40, // 攻击4 + 0x05 => 50, // 攻击5 + 0x06 => 110, // 锁链1 + 0x07 => 120, // 锁链2 + 0x08 => 130, // 锁链3 + _ => 0 + }; + _pd.AddPriority(tidx, pdVal); + if (_pd.ActionCount != 8) return; + sa.DebugMsg($"P3B_小电视_记录头标:头标记录完毕", Debugging); + sa.DebugMsg($"{_pd.ShowPriorities()}", Debugging); + _p3.小电视头标记录.Set(); // 头标记录 + } + } + + [ScriptMethod(name: "P3B_小电视_光头扫描方向记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3159[56])$"], + userControl: Debugging)] + public void P3B_小电视_光头扫描方向记录(Event ev, ScriptAccessory sa) + { + if (_parse != 3.1) return; + const uint CANNON_RIGHT = 31595; + _p3.光头小电视方向打右 = ev.ActionId == CANNON_RIGHT; + _p3.光头扫描方向记录.Set(); + } + + [ScriptMethod(name: "P3B_小电视_钢铁范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3159[56])$"], + userControl: true)] + public void P3B_小电视_钢铁范围(Event ev, ScriptAccessory sa) + { + if (_parse != 3.1) return; + foreach (var member in sa.Data.PartyList) + sa.DrawCircle(member, 4000, 6000, $"P3B_小电视_钢铁范围", 7f); + } + + [ScriptMethod(name: "P3B_小电视_指路", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(345[23])$"], + userControl: true, suppress: 1000)] + public void P3B_小电视_指路(Event ev, ScriptAccessory sa) + { + if (_parse != 3.1) return; + var hasMarker = _p3.小电视头标记录.WaitOne(2500); + if (!hasMarker) + { + sa.DebugMsg($"未在一定时间内检测到头标,启用HTDH优先级排列", Debugging); + for (int i = 0; i < sa.Data.PartyList.Count; i++) + { + var obj = sa.GetById(sa.Data.PartyList[i]); + if (obj is null) continue; + if (!((IBattleChara)obj).HasStatusAny([3452, 3453])) continue; + _pd.AddPriority(i, 100); + } + } + _p3.光头扫描方向记录.WaitOne(); + + // 打右左安全,攻1-5,锁1-3 + List staticPos = + [ + new(99.0f, 0, 91.0f), new(110.0f, 0, 100.0f), new(118.0f, 0, 100.0f), new(99.0f, 0, 109.0f), new(99.0f, 0, 119.0f), + new(93.0f, 0, 82.0f), new(86.0f, 0, 92.5f), new(86.0f, 0, 107.5f) + ]; + + var myRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + var myPos = staticPos[myRank]; + if (!_p3.光头小电视方向打右 && myRank is 0 or 3 or 4 or 5) + myPos = myPos.FoldPointHorizon(Center.X); + sa.DrawGuidance(myPos, 0, 10000, $"P3B_小电视_指路"); + } + + [ScriptMethod(name: "P3B_小电视_面向计算", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(345[23])$"], userControl: Debugging)] + public void P3B_小电视_面向计算(Event ev, ScriptAccessory sa) + { + if (_parse != 3.1) return; + if (ev.TargetId != sa.Data.Me) return; + const uint PLAYER_CANNON_RIGHT = 3452; + + _p3.光头扫描方向记录.WaitOne(); + _p3.小电视头标记录.WaitOne(3000); + + int myBindRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()) - 5; + if (myBindRank < 0) return; + + // 基于光头扫右,玩家扫右 + var faceRegion = myBindRank switch + { + 0 => 0, // 锁1朝下 + 1 => 3, // 锁2朝左 + 2 => 1, // 锁3朝右 + _ => -1 + }; + if (faceRegion < 0) return; + faceRegion += (ev.StatusId != PLAYER_CANNON_RIGHT ? 2 : 0) + (myBindRank == 0 && !_p3.光头小电视方向打右 ? 2 : 0); + + _p3.小电视玩家面向 = (faceRegion + 4) % 4; + _p3.小电视玩家面向记录.Set(); + } + + [ScriptMethod(name: "P3B_小电视_面向箭头辅助绘图", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(345[23])$"], userControl: true)] + public void P3B_小电视_面向箭头辅助(Event ev, ScriptAccessory sa) + { + if (_parse != 3.1) return; + if (ev.TargetId != sa.Data.Me) return; + + _p3.小电视玩家面向记录.WaitOne(); + DrawFacingArrow(sa, _p3.小电视玩家面向, false, "P3B_小电视_面向箭头辅助_自身"); + DrawFacingArrow(sa, _p3.小电视玩家面向, true, "P3B_小电视_面向箭头辅助_正确面向"); + return; + + void DrawFacingArrow(ScriptAccessory sa, int dir, bool isSupport, string name) + { + var dp = sa.DrawLine(sa.Data.Me, 0, 0, 10000, name, + isSupport ? dir * 90f.DegToRad() : 0, 1f, 4.5f, + isSafe: isSupport, draw: false); + dp.FixRotation = isSupport; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + } + } + + [ScriptMethod(name: "*P3B_小电视_自动面向辅助", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3159[56])$"], userControl: true)] + public void P3B_小电视_自动面向辅助(Event ev, ScriptAccessory sa) + { + if (_parse != 3.1) return; + if (!SpecialMode) return; + + // 1. 判断进入条件 + _p3.小电视玩家面向记录.WaitOne(); + int myBindRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()) - 5; + if (myBindRank < 0) return; + + var myObject = sa.Data.MyObject; + if (myObject == null) return; + + // 2. 设置正确面向 + int correctFaceDir = _p3.小电视玩家面向; + sa.DebugMsg($"小电视玩家面向应为 {correctFaceDir} ", Debugging); + float correctFaceRotation = correctFaceDir * 90f.DegToRad(); + + // 3. 开启触发 + _p3.小电视面向辅助Framework = sa.Method.RegistFrameworkUpdateAction(Action); + return; + + void Action() + { + var myRotation = myObject.Rotation; + var rotationDiff = MathF.Abs(myRotation.GetDiffRad(correctFaceRotation)); + if (!sa.IsMoving() && rotationDiff > 0.1f && (DateTime.Now - _p3.小电视面向辅助触发时间).TotalMilliseconds > 250) + { + _p3.小电视面向辅助触发时间 = DateTime.Now; + sa.SetRotation(myObject, correctFaceRotation); + } + } + } + + [ScriptMethod(name: "P3B_小电视_处理结束", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3159[56])$"], + userControl: Debugging)] + public void P3B_小电视_处理结束(Event ev, ScriptAccessory sa) + { + if (_parse != 3.1) return; + _p3.小电视头标记录.Reset(); + _p3.光头扫描方向记录.Reset(); + _p3.小电视玩家面向记录.Reset(); + sa.Method.RemoveDraw($"P3B_小电视.*"); + sa.Method.UnregistFrameworkUpdateAction(_p3.小电视面向辅助Framework); + } + + #endregion P3B 小电视 + + #region P4 蓝屏 + + [ScriptMethod(name: "———————— 《P4 蓝屏》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P4_蓝屏_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P4_蓝屏_分P", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31559)$"], + userControl: Debugging)] + public void P4_蓝屏_分P(Event ev, ScriptAccessory sa) + { + _parse = 4; + _p3.Reset(sa); + _p3.Dispose(); + _p4.Register(); + _p4.BossId = ev.SourceId; + } + + [ScriptMethod(name: "P4_蓝屏_每轮波动炮初始化", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3161[05])$"], userControl: Debugging, suppress: 1000)] + public void P4_蓝屏_每轮波动炮初始化(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + sa.Method.RemoveDraw($"P4_蓝屏_R{_p4.蓝屏波动炮轮数}.*"); + + _p4.蓝屏波动炮轮数++; + _pd.Init(sa, $"P4蓝屏R{_p4.蓝屏波动炮轮数}"); + _pd.AddPriorities([1, 8, 3, 6, 4, 5, 2, 7]); + sa.DebugMsg($"现在是蓝屏波动炮 第{_p4.蓝屏波动炮轮数}轮", Debugging); + _p4.波动炮初始化记录.Set(); + } + + [ScriptMethod(name: "P4_蓝屏_波动炮八方指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3161[05])$"], + userControl: true, suppress: 1000)] + public void P4_蓝屏_波动炮八方指路(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + _p4.波动炮初始化记录.WaitOne(); + + lock (_p4) + { + if (_p4.蓝屏波动炮轮数 > 3) return; + List partySpreadRegion = [11, 5, 13, 3, 14, 2, 12, 4]; + var pos = new Vector3(100, 0, 114f).RotateAndExtend(Center, partySpreadRegion[sa.GetMyIndex()] * 22.5f.DegToRad()); + sa.DrawGuidance(pos, 0, 5000, $"P4_蓝屏_波动炮八方指路"); + for (int i = 0; i < 8; i++) + { + var dp = sa.DrawLine(Center, 0, 0, 10000, $"P4_蓝屏_波动炮指引线{i}", partySpreadRegion[i] * 22.5f.DegToRad(), 20f, 20f, draw: false); + dp.Color = i switch + { + 0 or 1 => new Vector4(0.1f, 0.1f, 1, 1), + 2 or 3 => new Vector4(0.1f, 1f, 0.1f, 1), + _ => new Vector4(1, 0.1f, 0.1f, 1), + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + } + } + + [ScriptMethod(name: "*P4_蓝屏_波动炮八方指路移除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31614"], userControl: Debugging)] + public void P4_蓝屏_波动炮八方指路移除(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + sa.Method.RemoveDraw($"P4_蓝屏_波动炮.*"); + _p4.波动炮初始化记录.Reset(); + } + + [ScriptMethod(name: "P4_蓝屏_地震", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3156[789]|31570)$", "TargetIndex:1"], + userControl: true)] + public void P4_蓝屏_地震(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + const uint CHARIOT = 31567, DONUT_1 = 31568, DONUT_2 = 31569, DONUT_3 = 31570; + + (int outScale, int innerScale, int donutCount) = ev.ActionId switch + { + CHARIOT => (12, 6, 1), + DONUT_1 => (18, 12, 2), + DONUT_2 => (24, 18, 3), + _ => (0, 0, 4) + }; + + string prefix = "P4_蓝屏_地震月环"; + sa.Method.RemoveDraw($"{prefix}{donutCount-1}"); + if (donutCount == 4) return; + + var dp = sa.DrawDonut(Center, 0, 10000, $"{prefix}{donutCount}", outScale, innerScale, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(2.5f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + + [ScriptMethod(name: "P4_蓝屏_波动炮分摊目标记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(22393)$"], userControl: Debugging)] + public void P4_蓝屏_波动炮分摊目标记录(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + lock (_pd) + _pd.AddPriority(sa.GetPlayerIdIndex((uint)ev.TargetId), 10); + } + + [ScriptMethod(name: "P4_蓝屏_分摊绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31614"], userControl: true, suppress: 1000)] + public void P4_蓝屏_分摊绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + + var myPriRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + var mySafeBool = myPriRank is 0 or 1 or 2 or 6 ? 0 : 1; + for (int i = 0; i < 2; i++) + { + var targetIdx = _pd.SelectSpecificPriorityIndex(i+6).Key; + sa.DrawRect(_p4.BossId, sa.Data.PartyList[targetIdx], 0, 10000, + $"P4_蓝屏_R{_p4.蓝屏波动炮轮数}_分摊绘图", 0, 6, 50, isSafe: i == mySafeBool); + } + } + + [ScriptMethod(name: "P4_蓝屏_分摊指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31614"], userControl: true, suppress: 1000)] + public void P4_蓝屏_分摊指路(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + sa.DrawLine(Center, 0, 0, 6000, $"P4_蓝屏_R{_p4.蓝屏波动炮轮数}_分摊指引线1", 20f.DegToRad(), 20f, 20f, isSafe: true, draw: true); + sa.DrawLine(Center, 0, 0, 6000, $"P4_蓝屏_R{_p4.蓝屏波动炮轮数}_分摊指引线2", -20f.DegToRad(), 20f, 20f, isSafe: true, draw: true); + + sa.DebugMsg(_pd.ShowPriorities(), Debugging); + + var myPriRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + var myRegion = myPriRank is 0 or 1 or 2 or 6 ? -1 : 1; + var extend = 0; // TODO,不同轮数分摊位置是不同的 + var myPos = new Vector3(100, 0, 114).RotateAndExtend(Center, myRegion * 20f.DegToRad(), extend); + sa.DrawGuidance(myPos, 0, 10000, $"P4_蓝屏_R{_p4.蓝屏波动炮轮数}_分摊指路"); + } + + [ScriptMethod(name: "P4_蓝屏_第二段波动炮", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31616"], userControl: true)] + public void P4_蓝屏_第二段波动炮(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + sa.DrawRect(ev.SourceId, 0, 10000, $"P4_蓝屏_第二段波动炮", 0, 6, 50); + } + + [ScriptMethod(name: "P4_蓝屏_第二段波动炮移除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31616"], userControl: Debugging, suppress: 1000)] + public void P4_蓝屏_第二段波动炮移除(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + sa.Method.RemoveDraw($"P4_蓝屏_第二段波动炮"); + } + + [ScriptMethod(name: "*P4_蓝屏_屏蔽场地月环特效", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3156[689]|31570)$", "TargetIndex:1"], userControl: true)] + public void P4_蓝屏_屏蔽特效(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + if (!SpecialMode) return; + sa.WriteVisible(sa.GetById((uint)ev.SourceId), false); + } + + [ScriptMethod(name: "*P4_蓝屏_屏蔽第一段波动炮特效", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31614"], userControl: true)] + public void P4_蓝屏_屏蔽第一段波动炮特效(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + if (!SpecialMode) return; + sa.WriteVisible(sa.GetById(ev.SourceId), false); + } + + [ScriptMethod(name: "*P4_蓝屏_屏蔽第二段波动炮特效", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31616"], userControl: true)] + public void P4_蓝屏_屏蔽第二段波动炮特效(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + if (!SpecialMode) return; + sa.WriteVisible(sa.GetById(ev.SourceId), false); + } + + [ScriptMethod(name: "*P4_蓝屏_屏蔽分摊波动炮特效", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31615"], userControl: true)] + public void P4_蓝屏_屏蔽分摊波动炮特效(Event ev, ScriptAccessory sa) + { + if (_parse != 4) return; + if (!SpecialMode) return; + sa.WriteVisible(sa.GetById(ev.SourceId), false); + } + + #endregion P4 蓝屏 + + #region P5A 一运一传 + + [ScriptMethod(name: "———————— 《P5A1 一运》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5_一运_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5_开场_分P", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31621"], + userControl: Debugging)] + public void P5_开场_分P(Event ev, ScriptAccessory sa) + { + _parse = 5.0; + } + + [ScriptMethod(name: "P5A1_一运_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31624"], + userControl: Debugging)] + public void P5A1_一运_分P(Event ev, ScriptAccessory sa) + { + _parse = 5.1; + + _p4.Reset(sa); + _p4.Dispose(); + _p5A.Register(); + + ResetSupportUnitVisibility(sa); + _pd.Init(sa, "P5一运"); + _pd.AddPriorities([0, 1, 2, 3, 4, 5, 6, 7]); // 依职能顺序添加优先值 + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "P5A1_一运_眼睛激光", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:800375AC", "Id:00020001"])] + public void P5A1_一运_眼睛激光(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + var rot = ev.Index() - 1; + var basePos = new Vector3(100, 0, 80); + var eyePos = basePos.RotateAndExtend(Center, -45f.DegToRad() * rot); + sa.DrawRect(eyePos, Center, 7500, 12500, "P5A1_一运_眼睛激光", 0, 16, 40); + } + + [ScriptMethod(name: "P5A1_一运_眼睛激光删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31521"], userControl: Debugging)] + public void P5A1_一运_眼睛激光删除(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + sa.Method.RemoveDraw($"P5A1_一运_眼睛激光"); + } + + [ScriptMethod(name: "P5A1_一运_远线记录", eventType: EventTypeEnum.Tether, eventCondition: ["Id:00C9"], userControl: Debugging)] + public void P5A1_一运_远线记录(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + + lock (_pd) + { + var targetId = ev.TargetId; + var targetIdx = sa.GetPlayerIdIndex((uint)targetId); + var sourceId = ev.SourceId; + var sourceIdx = sa.GetPlayerIdIndex((uint)sourceId); + var pdValMax = _pd.SelectSpecificPriorityIndex(0, true).Value; + + // 添加优先级值前,先获得优先级字典内的最值(即检查是否添加过),将两组远线搭档分别+1000/+2000 + _pd.AddPriority(targetIdx, pdValMax >= 1000 ? 2000 : 1000); + _pd.AddPriority(sourceIdx, pdValMax >= 1000 ? 2000 : 1000); + + _p5A.远线搭档记录.Set(); + } + } + + [ScriptMethod(name: "P5A1_一运_记录头标", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[123467])$"], + userControl: Debugging)] + public void P5A1_一运_记录头标(Event ev, ScriptAccessory sa) + { + // 只取攻击1234与锁链12 + if (_parse != 5.1) return; + + lock (_pd) + { + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var targetJob = sa.GetPlayerJobByIndex(tidx); + sa.DebugMsg($"检测到{targetJob} 被标 ev.Id {mark}", Debugging); + _pd.AddActionCount(); + var pdVal = mark switch + { + 1 => 10, // 攻击1 + 2 => 20, // 攻击2 + 3 => 30, // 攻击3 + 4 => 40, // 攻击4 + 6 => 100, // 锁链1 + 7 => 200, // 锁链2 + _ => 0 + }; + _pd.AddPriority(tidx, pdVal); + if (_pd.ActionCount != 6) return; + sa.DebugMsg($"P5_一运_记录头标:头标记录完毕", Debugging); + sa.DebugMsg($"{_pd.ShowPriorities()}", Debugging); + _p5A.头标记录.Set(); // 头标记录 + } + } + + [ScriptMethod(name: "P5A1_一运_定位光头", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["SourceDataId:14669", "Id:7747"], + userControl: Debugging)] + public void P5A1_一运_定位光头(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + var spos = ev.SourcePosition; + var dir = spos.GetRadian(Center).RadianToRegion(4, isDiagDiv: true); + _p5A.光头位置 = dir; + _p5A.蟑螂位置 = (dir + 2) % 4; + sa.DebugMsg($"一运光头位置:{_p5A.光头位置},一运蟑螂位置:{_p5A.蟑螂位置}", Debugging); + _p5A.光头蟑螂定位.Set(); + } + + [ScriptMethod(name: "P5A1_一运_初始位置指路", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["SourceDataId:14669", "Id:7747"], + userControl: true)] + public void P5A1_一运_初始位置指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + + _p5A.远线搭档记录.WaitOne(5000); + _p5A.头标记录.WaitOne(2000); + _p5A.光头蟑螂定位.WaitOne(2000); + + var myIndex = sa.GetMyIndex(); + var myPdVal = _pd.Priorities[myIndex]; + + // 远线无标玩家自动匹配搭档,并修正优先级 + if (IsFarLineWithoutMarker(myPdVal)) + { + int partnerIndex = FindFarLinePartnerIndex(myPdVal); + int partnerChain = _pd.Priorities[partnerIndex].GetDecimalDigit(3); // 1 or 2 + _pd.AddPriority(myIndex, partnerChain * 100 + 10); // 11 or 21 + sa.DebugMsg($"远线无标玩家找到搭档:{sa.GetPlayerJobByIndex(partnerIndex)}", Debugging); + } + + // 2. 统一优先级 markerVal + for (int i = 0; i < 8; i++) + _pd.Priorities[i] = (_pd.Priorities[i] % 1000) / 10; + + int markerVal = _pd.Priorities[myIndex]; + sa.DebugMsg($"经矫正,markerVal = {markerVal}", Debugging); + + // 3. 计算待命点 + Vector3 wait1 = CalcWaitPoint(markerVal, isLeft: true ); + Vector3 wait2 = CalcWaitPoint(markerVal, isLeft: false); + + // 4. 画指引 + sa.DrawGuidance(wait1, 0, 5000, "P5A1_一运_待命地点1"); + sa.DrawGuidance(wait2, 0, 5000, "P5A1_一运_待命地点2"); + + // 5. 重置信号量 + _p5A.远线搭档记录.Reset(); + _p5A.头标记录.Reset(); + _p5A.光头蟑螂定位.Reset(); + + return; + + bool IsFarLineWithoutMarker(int pd) => pd >= 1000 && pd.GetDecimalDigit(3) == 0; + + int FindFarLinePartnerIndex(int myPd) + { + // 千位 1 -> 找降序第3(index 2);千位 2 -> 找降序第1(index 0) + int thousand = myPd / 1000; + return _pd.SelectSpecificPriorityIndex(thousand == 2 ? 0 : 2, true).Key; + } + + Vector3 CalcWaitPoint(int marker, bool isLeft) + { + Vector3 raw = isLeft ? new Vector3(90f, 0, 106f) : new Vector3(110f, 0, 106f); + + bool needBack = marker is 3 or 4 or 20 or 21; // 靠外组 + bool useOmega = marker is 10 or 11 or 20 or 21; // 远线组 + int dir = useOmega ? _p5A.光头位置 : _p5A.蟑螂位置; + + if (needBack) raw += new Vector3(0, 0, 8); + return raw.RotateAndExtend(Center, 90f.DegToRad() * dir); + } + } + + [ScriptMethod(name: "P5A1_一运_记录拳头", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(157(09|10))$"], userControl: Debugging)] + public void P5A1_一运_记录拳头(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + + const uint BLUE_ROCKET = 15709; + // const uint YELLOW_ROCKET = 15710; + + var myPos = sa.Data.MyObject.Position; + + // 1. 计算玩家四分之一半场 + if (_p5A.玩家四分之一半场 < 0) + _p5A.玩家四分之一半场 = myPos.GetRadian(Center).RadianToRegion(4, isDiagDiv: false); + + // 2. 判断拳头四分之一半场 + int mobRegion = ev.SourcePosition.GetRadian(Center).RadianToRegion(4, isDiagDiv: false); + if (mobRegion != _p5A.玩家四分之一半场) return; + + // 3. 统计 + lock (_pd) + { + _pd.AddActionCount(); + _p5A.拳头数量++; + _p5A.拳头颜色 += ev.DataId() == BLUE_ROCKET ? 1 : -1; + + sa.DebugMsg($"玩家半场刷出第{_p5A.拳头数量}个拳头," + $"颜色{(ev.DataId() == BLUE_ROCKET ? "蓝" : "黄")}," + $"颜色累计值={_p5A.拳头颜色}", Debugging); + } + + // 4. 14 只怪(6 头标 + 8 拳头)全部刷完 + if (_pd.ActionCount != 14) return; + _p5A.拳头记录.Set(); + sa.Method.RemoveDraw($"P5A1_一运_待命地点.*"); + } + + [ScriptMethod(name: "P5A1_一运_拳头待命指路", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(157(09|10))$"], + userControl: true, suppress: 10000)] + public void P5A1_一运_拳头待命指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + _p5A.拳头记录.WaitOne(2000); + try + { + var myIndex = sa.GetMyIndex(); + var markerVal = _pd.Priorities[myIndex]; + + var isOutside = markerVal is 3 or 4 or 20 or 21; // 攻3,攻4,锁2,禁2 + var isRemoteTetherOutside = markerVal is 20 or 21; // 锁2,禁2 + + // var myQuadrant = _p5A.玩家四分之一半场; + var punchCountAtMyQuadrant = _p5A.拳头数量; + var punchColorAtMyQuadrant = _p5A.拳头颜色; + + // 1. 内场玩家,不交换,直接站象限点 + if (!isOutside) + { + // isRemoteTetherOutside 决定玩家是靠近远线组(欧米茄)还是靠近近线组(蟑螂) + Vector3 pos = GetQuadrantPoint(isRemoteTetherOutside, _p5A.光头位置, _p5A.玩家四分之一半场); + sa.DrawGuidance(pos, 0, 5000, $"P5A1_一运_拳头"); + return; + } + + // 2. 拳头数量不对,不绘图 + if (punchCountAtMyQuadrant != 2) + { + // 缺人,不给具体坐标 + sa.Method.TextInfo("观察同组拳头颜色是否交换", 4000, true); + return; + } + + // 3. 检测拳头是否同色,镜像换点 + bool needSwap = punchColorAtMyQuadrant != 0; + Vector3 final = GetQuadrantPoint(isRemoteTetherOutside, _p5A.光头位置, _p5A.玩家四分之一半场); + if (needSwap) + final = MirrorAcrossBoss(final, _p5A.光头位置); + sa.DrawGuidance(final, 0, 5000, $"P5A1_一运_拳头"); + } + finally + { + _p5A.拳头记录.Reset(); + } + return; + + Vector3 GetQuadrantPoint(bool isRemoteTetherOutside, int omegaDir, int myQuadrant) + { + // 所有坐标以第0象限(右下)为原型,后面统一做象限镜像 + Vector3 raw = isRemoteTetherOutside ? new Vector3(102.7f, 0, 110f) : new Vector3(108.7f, 0, 110f); + + // Boss 在奇数方向时整体顺时针旋转 -90° + if (omegaDir % 2 == 1) + raw = raw.RotateAndExtend(new Vector3(109.9f, 0, 110f), -90f.DegToRad()); + + raw = myQuadrant switch + { + 1 => raw.FoldPointVertical(Center.Z), + 2 => raw.PointCenterSymmetry(Center), + 3 => raw.FoldPointHorizon(Center.X), + _ => raw, + }; + + return raw; + } + + Vector3 MirrorAcrossBoss(Vector3 pos, int omegaDir) + { + return omegaDir % 2 == 0 + ? pos.FoldPointHorizon(Center.X) + : pos.FoldPointVertical(Center.Z); + } + } + + [ScriptMethod(name: "P5A1_一运_激光手旋转引导位置", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(009[CD])$"], userControl: true)] + public void P5A1_一运_激光手旋转引导位置(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + const int ICON_ROTATE_CW = 156; // 009C + + lock (_p5A.激光手方向字典) + { + bool isCw = ev.Id0() == ICON_ROTATE_CW; + ulong tid = ev.TargetId; + Vector3 tpos = sa.GetById(tid)?.Position ?? Center; + Vector3 baitPos = tpos.RotateAndExtend(Center, (isCw ? 5f : -5f).DegToRad(), -1f); + int region = tpos.GetRadian(Center).RadianToRegion(12, isDiagDiv: true); + + sa.DebugMsg($"激光手方向字典中添加:Key: {region} / 12, Value: {baitPos}", Debugging); + _p5A.激光手方向字典.TryAdd(region, baitPos); + + var dp = sa.DrawCircle(baitPos, 0, 10000, $"P5A1_一运_激光手旋转引导位置", 0.5f, isSafe: true, draw: false); + dp.Color = sa.Data.DefaultSafeColor.WithW(3f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "P5A1_一运_玩家引导激光手指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31587"], userControl: true)] + public void P5A1_一运_玩家引导激光手指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + if (ev.TargetId != sa.Data.Me) return; + + // 这一条判断会因为suppress被return回去,所以suppress的使用场景需注意,事件确认可被触发。否则就用bool。 + if (_p5A.第一根远线拉断) return; + _p5A.第一根远线拉断 = true; + _p5A.开启玩家引导激光手指路 = true; + + // 1. 基础数据 + int myIndex = sa.GetMyIndex(); + int marker = _pd.Priorities[myIndex]; + Vector3 myPos = ev.TargetPosition; + + bool isShieldTarget = marker is 10 or 11; // 锁1 禁1,需往场中被盾击 + bool isOutside = marker is 3 or 4 or 20 or 21; // 攻3,攻4,锁2,禁2,偏场外 + bool isBind = marker is 10 or 20 or 11 or 21; // 锁1,锁2,禁1,禁2 + + // 2. 计算光头12方向与手臂编号 + int omega12 = _p5A.光头位置 * 3; // 0~11 + int armUnit = CalcArmUnit(omega12, isOutside, isBind, myPos, Center); + _p5A.玩家引导激光手方位 = armUnit; + + Vector3 guidePos = isShieldTarget + ? new Vector3(100, 0, 105).RotateAndExtend(Center, armUnit * 30f.DegToRad()) + : _p5A.激光手方向字典[armUnit]; + + sa.DrawGuidance(guidePos, 0, 4000, isShieldTarget ? "P5A1_一运_玩家引导激光手指路_场中" : "P5A1_一运_玩家引导激光手指路_拳头", isSafe: false); + return; + + int CalcArmUnit(int omega12, bool outside, bool bind, Vector3 myPos, Vector3 center) + { + Vector3 omegaPos = new Vector3(100, 0, 120).RotateAndExtend(center, omega12 * 30f.DegToRad()); + bool isRight = IsAtRight(omegaPos, myPos, center); + // 3-bit 编码 + int code = (outside ? 4 : 0) | (isRight ? 2 : 0) | (bind ? 1 : 0); + + return code switch + { + 0b111 => (omega12 + 1) % 12, + 0b110 => (omega12 + 5) % 12, + 0b101 => (omega12 + 11) % 12, + 0b100 => (omega12 + 7) % 12, + 0b010 => (omega12 + 3) % 12, + 0b000 => (omega12 + 9) % 12, + + 0b011 => (omega12 + 3) % 12, + 0b001 => (omega12 + 9) % 12, + _ => -1 + }; + } + + bool IsAtRight(Vector3 posReference, Vector3 posTarget, Vector3 posCenter) => + posTarget.GetRadian(posCenter).GetDiffRad(posReference.GetRadian(posCenter)) > 0; + } + + [ScriptMethod(name: "P5A1_一运_玩家引导激光手指路刷新", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31482"], + userControl: Debugging, suppress: 10000)] + public void P5A1_一运_玩家引导激光手指路刷新(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + sa.Method.RemoveDraw($"P5A1_一运_玩家引导激光手指路.*"); + + if (!_p5A.开启玩家引导激光手指路) return; + + var myIndex = sa.GetMyIndex(); + var markerVal = _pd.Priorities[myIndex]; + var isShieldTarget = markerVal is 10 or 11; + if (isShieldTarget) return; + + sa.DebugMsg($"玩家引导激光手方位为:{_p5A.玩家引导激光手方位} / 12", Debugging); + Vector3 armUnitPos = _p5A.激光手方向字典[_p5A.玩家引导激光手方位]; + + sa.DrawGuidance(armUnitPos, 0, 4000, $"P5A1_一运_引导拳头"); + } + + [ScriptMethod(name: "P5A1_一运_转转手引导指路删除", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31600"], + userControl: Debugging, suppress: 10000)] + public void P5A1_一运_转转手引导指路删除(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + sa.Method.RemoveDraw($"P5A1_一运_引导拳头"); + } + + [ScriptMethod(name: "P5A1_一运_玩家场中盾引导指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31482"], + userControl: true, suppress: 10000)] + public void P5A1_一运_玩家场中盾引导指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + var myIndex = sa.GetMyIndex(); + var markerVal = _pd.Priorities[myIndex]; + + if (markerVal is not 10 and not 11) return; + var myArmUnit = _p5A.玩家引导激光手方位; + + var centerBiasPos = new Vector3(100, 0, 105).RotateAndExtend(Center, myArmUnit * 30f.DegToRad()); + sa.DrawGuidance(centerBiasPos, 0, 3000, $"P5A1_一运_场中盾连击引导"); + } + + [ScriptMethod(name: "P5A1_一运_转转手引导后近线待命指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31600"], + userControl: true, suppress: 10000)] + public void P5A1_一运_转转手引导后近线待命指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + var myIndex = sa.GetMyIndex(); + var markerVal = _pd.Priorities[myIndex]; + + if (markerVal is not 1 and not 2 and not 3 and not 4) return; + var myArmUnit = _p5A.玩家引导激光手方位; + + var standByPos = new Vector3(100, 0, 114). + RotateAndExtend(Center, MathF.Round((float)myArmUnit * 2 / 3) * 45f.DegToRad()); + sa.DrawGuidance(standByPos, 0, 6000, $"P5A1_一运_转转手引导后近线待命指路"); + } + + [ScriptMethod(name: "P5A1_一运_光头左右扫描记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3163[89])$"], + userControl: Debugging)] + public void P5A1_一运_光头左右扫描记录(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + const uint OMEGA_RIGHT_CANNON = 31638; + _p5A.光头左右扫描 = ev.ActionId == OMEGA_RIGHT_CANNON ? 1 : 2; + } + + [ScriptMethod(name: "P5A1_一运_玩家小电视Buff记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(345[23])$"], + userControl: Debugging)] + public void P5A1_一运_玩家小电视Buff记录(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + _pd.AddPriority(tidx, 100); // 小电视点名+100 + const uint PLAYER_RIGHT_CANNON = 3452; + _p5A.玩家左右扫描 = ev.StatusId == PLAYER_RIGHT_CANNON ? 1 : 2; // 右刀1,左刀2 + } + + [ScriptMethod(name: "P5A1_一运_盾连击目标记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31528", "TargetIndex:1"], + userControl: Debugging)] + public void P5A1_一运_盾连击目标记录(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + sa.Method.RemoveDraw($"P5A1_一运_场中盾连击引导"); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + _pd.AddPriority(tidx, 1000); // 盾连击目标+1000 + _p5A.盾连击记录.Set(); + } + + [ScriptMethod(name: "P5A1_一运_分摊与小电视指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31528"], + userControl: true)] + public void P5A1_一运_分摊与小电视指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + _p5A.盾连击记录.WaitOne(1000); + + try + { + int myIndex = sa.GetMyIndex(); + int myPriVal = _pd.Priorities[myIndex]; + int markerVal = myPriVal % 100; // 仅头标 + + // 1. 近线组直接退出 + if (markerVal is 1 or 2 or 3 or 4) return; + + // 2. 是否同目标,小电视点名+100、盾连击+1000 + bool isSameTarget = _pd.SelectSpecificPriorityIndex(0, true).Value >= 1100; + + // 3. 计算原始坐标(以光头在C、右刀为基准) + Vector3 shieldPos = new(99f + (isSameTarget ? -3.5f : 0), 0, 115f); + Vector3 stackPos = new(99f + (isSameTarget ? 0 : -3.5f), 0, 100f); + sa.DebugMsg($"分摊位置:{stackPos},盾连击位置:{shieldPos}", Debugging); + + // 4. 左右刀镜像 + if (_p5A.光头左右扫描 == 2) // 1 右 2 左 + { + shieldPos = shieldPos.FoldPointHorizon(Center.X); + stackPos = stackPos.FoldPointHorizon(Center.X); + } + + // 5. 旋转到光头当前方向 + float rotRad = _p5A.光头位置 * 90f.DegToRad(); + shieldPos = shieldPos.RotateAndExtend(Center, rotRad); + stackPos = stackPos.RotateAndExtend(Center, rotRad); + + // 6. 画指引 + bool isShield = myPriVal / 1000 == 1; + sa.DrawGuidance(isShield ? shieldPos : stackPos, 0, 5000, isShield ? "P5A1_一运_盾连击指路" : "P5A1_一运_分摊指路"); + + // 7. 画小电视面向辅助 + bool isTv = myPriVal % 1000 >= 100; + if (isTv) + { + int faceDir = (_p5A.光头位置 + (_p5A.光头左右扫描 != _p5A.玩家左右扫描 ? 2 : 0)) % 4; + DrawFacingArrow(sa, faceDir, true, "P5A1_一运_小电视面向辅助_正确面向"); + DrawFacingArrow(sa, faceDir, false, "P5A1_一运_小电视面向辅助_自身"); + sa.Method.TextInfo("小电视,站在外侧", 3000, true); + } + else + { + sa.Method.TextInfo("躲避小电视,站在内侧", 3000, true); + } + + } + finally + { + _p5A.盾连击记录.Reset(); + } + return; + + void DrawFacingArrow(ScriptAccessory sa, int dir, bool isSupport, string name) + { + var dp = sa.DrawLine(sa.Data.Me, 0, 0, 5000, name, + isSupport ? dir * 90f.DegToRad() : 0, 1f, 4.5f, + isSafe: isSupport, draw: false); + dp.FixRotation = isSupport; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + } + } + + [ScriptMethod(name: "*P5A1_一运_小电视自动面向辅助", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3163[89])$"], userControl: true)] + public void P5A1_一运_小电视自动面向辅助(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + if (!SpecialMode) return; + + // 1. 判断进入条件 + int myIndex = sa.GetMyIndex(); + int myPriVal = _pd.Priorities[myIndex]; + bool isTv = myPriVal % 1000 >= 100; + if (!isTv) return; + + var myObject = sa.Data.MyObject; + if (myObject == null) return; + + // 2. 设置正确面向 + int correctFaceDir = (_p5A.光头位置 + (_p5A.光头左右扫描 != _p5A.玩家左右扫描 ? 2 : 0)) % 4; + float correctFaceRotation = correctFaceDir * 90f.DegToRad(); + + // 3. 开启触发 + _p5A.小电视面向辅助Framework = sa.Method.RegistFrameworkUpdateAction(Action); + return; + + void Action() + { + var myRotation = myObject.Rotation; + var rotationDiff = MathF.Abs(myRotation.GetDiffRad(correctFaceRotation)); + if (!sa.IsMoving() && rotationDiff > 0.1f && (DateTime.Now - _p5A.小电视面向辅助触发时间).TotalMilliseconds > 250) + { + _p5A.小电视面向辅助触发时间 = DateTime.Now; + sa.SetRotation(myObject, correctFaceRotation); + } + } + } + + [ScriptMethod(name: "P5A1_一运_小电视自动面向辅助关闭", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3163[89])$"], userControl: Debugging)] + public void P5A1_一运_小电视自动面向辅助关闭(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + sa.Method.UnregistFrameworkUpdateAction(_p5A.小电视面向辅助Framework); + } + + [ScriptMethod(name: "P5A1_一运_绘图删除准备一传", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31529)$"], + userControl: Debugging)] + public void P5A1_一运_绘图删除准备一传(Event ev, ScriptAccessory sa) + { + if (_parse != 5.1) return; + _parse = 5.15; + sa.Method.RemoveDraw("P5A1_一运.*"); + + for (int i = 0; i < 8; i++) + { + _pd.Priorities[i] %= 100; // 保留个位与十位,即删除小电视与盾连击记录 + } + sa.DebugMsg($"一传:经矫正,{_pd.ShowPriorities()}", Debugging); + } + + [ScriptMethod(name: "———————— 《P5A2 一传》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5A2_一传_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5A2_一传_蟑螂左右刀记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3163[67])$"], + userControl: Debugging)] + public void P5A2_一传_蟑螂左右刀记录(Event ev, ScriptAccessory sa) + { + if (_parse != 5.15) return; + const uint BEETLE_RIGHT_CLEAVE = 31636; + _p5A.蟑螂左右刀 = ev.ActionId == BEETLE_RIGHT_CLEAVE ? 1 : 2; + _p5A.蟑螂左右刀记录.Set(); + } + + [ScriptMethod(name: "P5A2_一传_蟑螂左右刀", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3163[67])$"], + userControl: true)] + public void P5A2_一传_蟑螂左右刀(Event ev, ScriptAccessory sa) + { + if (_parse != 5.15) return; + const uint BEETLE_RIGHT_CLEAVE = 31636; + var rot = ev.ActionId == BEETLE_RIGHT_CLEAVE ? -float.Pi / 2 : float.Pi / 2; + sa.DrawFan(ev.SourceId, 0, 10000, $"P5A2_一传_蟑螂左右刀", 210f.DegToRad(), rot, 90, 0); + } + + [ScriptMethod(name: "P5A2_一传_指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3163[67])$"], + userControl: true)] + public void P5A25_一传_指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.15) return; + _p5A.蟑螂左右刀记录.WaitOne(); + try + { + int myIndex = sa.GetMyIndex(); + int marker = _pd.Priorities[myIndex]; + + // 1. 配置表:marker -> 坐标(以蟑螂右刀、蟑螂在C为基准) + // 后续只改这里即可 + Vector3[] basePos = + { + new(98f, 0, 119f), // 1 Atk1 FarTarget + new(89.4f, 0, 83.8f), // 2 Atk2 FarTarget + new(91.1f, 0, 111.1f), // 3 Atk3 NearTarget + new(86.3f, 0, 113.7f), // 4 Atk4 NearTarget + new(80.5f, 0, 100f), // 10 Bind1 FarSource + new(93.5f, 0, 100f), // 20 Bind2 NearSource + new(83.8f, 0, 89f), // 11 Stop1 Idle + new(83.8f, 0, 89f) // 21 Stop2 Idle + }; + + // 2. 自动映射:marker到索引 + List map = [1, 2, 3, 4, 10, 20, 11, 21]; // 与 basePos 下标一一对应 + int idx = map.IndexOf(marker); + if (idx < 0) + { + sa.DebugMsg($"玩家标点信息{marker}读取错误", Debugging); + return; + } + + Vector3 pos = basePos[idx]; + + const int BEETLE_RIGHT_CLEAVE_RECORD = 1; + // 3. 左右刀镜像 + if (_p5A.蟑螂左右刀 != BEETLE_RIGHT_CLEAVE_RECORD) // 1 右 2 左 + pos = pos.FoldPointHorizon(Center.X); + + // 4. 方位旋转 + pos = pos.RotateAndExtend(Center, _p5A.蟑螂位置 * 90f.DegToRad()); + sa.DrawGuidance(pos, 0, 5000, "P5A2_一传_指路"); + } + finally + { + _p5A.蟑螂左右刀记录.Reset(); + } + } + + [ScriptMethod(name: "P5A2_一传_指路移除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3163[67])$"], + userControl: Debugging)] + public void P5A2_一传_指路移除(Event ev, ScriptAccessory sa) + { + if (_parse != 5.15) return; + sa.Method.RemoveDraw("P5A2_一传.*"); + } + + #endregion P5A 一运一传 + + #region P5B 二运二传 + + [ScriptMethod(name: "———————— 《P5B1 二运》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5B1_二运_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5B1_二运_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:32788"], + userControl: Debugging)] + public void P5B1_二运_分P(Event ev, ScriptAccessory sa) + { + _parse = 5.2; + + _p5A.Reset(sa); + _p5A.Dispose(); + _p5B.Register(); + + ResetSupportUnitVisibility(sa); + _pd.Init(sa, "P5二运"); + _pd.AddPriorities([0, 1, 2, 3, 4, 5, 6, 7]); // 依职能顺序添加优先值 + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "P5B1_二运_获取男人位置", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7747", "SourceDataId:15720"], userControl: Debugging)] + public void P5B1_二运_获取男人位置(Event ev, ScriptAccessory sa) + { + if (_parse != 5.2) return; + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(16, isDiagDiv: true); + _p5B.男人方位 = region; + sa.DebugMsg($"P5B1_二运_获取男人位置:{region} / 16", Debugging); + } + + [ScriptMethod(name: "P5B1_二运_获取远近线", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(342[78])$"], userControl: Debugging, suppress: 1000)] + public void P5B1_二运_获取远近线(Event ev, ScriptAccessory sa) + { + if (_parse != 5.2) return; + const uint MID_GLITCH = 3427, REMOTE_GLITCH = 3428; + _p5B.协作程序是远线 = ev.StatusId == REMOTE_GLITCH; + sa.DebugMsg($"记录下协作程序是 {(_p5B.协作程序是远线 ? "远" : "近")} 线", Debugging); + } + + [ScriptMethod(name: "P5B1_二运_获取八方头标", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[1234678]|11|12)$"], + userControl: Debugging)] + public void P5B1_二运_获取八方头标(Event ev, ScriptAccessory sa) + { + // 取攻击1234、锁链123、大饼 + if (_parse != 5.2) return; + + lock (_pd) + { + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var targetJob = sa.GetPlayerJobByIndex(tidx); + sa.DebugMsg($"检测到{targetJob} 被标 ev.Id {mark}", Debugging); + _pd.AddActionCount(); + var pdVal = mark switch + { + 0x01 => 10, // 攻击1 + 0x02 => 20, // 攻击2 + 0x03 => 30, // 攻击3 + 0x04 => 40, // 攻击4 + 0x06 => 80, // 锁链1 + 0x07 => 70, // 锁链2 + 0x08 => 60, // 锁链3 + 0x11 or 0x12 => 50, // 方块/大饼 + _ => 0 + }; + _pd.AddPriority(tidx, pdVal); + if (_pd.ActionCount != 8) return; + sa.DebugMsg($"P5B1_二运_获取八方头标:头标记录完毕", Debugging); + sa.DebugMsg($"{_pd.ShowPriorities()}", Debugging); + _p5B.八方头标记录完毕.Set(); // 头标记录 + } + } + + [ScriptMethod(name: "P5B1_二运_八方波动炮站位", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31603)$"], + userControl: true)] + public void P5B1_二运_八方波动炮站位(Event ev, ScriptAccessory sa) + { + if (_parse != 5.2) return; + _p5B.八方头标记录完毕.WaitOne(); + // 以男人方位为南,逆时针增加 + var myPriValRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + var myRegion = (_p5B.男人方位 + 1 + myPriValRank * 2) % 16; + _p5B.玩家八方方位 = myRegion; + sa.DebugMsg($"P5B1_二运_八方波动炮站位:玩家八方方位为 {myRegion} / 16", Debugging); + + var basePos = new Vector3(100f, 0f, _p5B.协作程序是远线 ? 119.75f : 111.75f); + var myExtend = myPriValRank switch + { + 0 or 7 when !_p5B.协作程序是远线 => 1, + 1 or 6 when _p5B.协作程序是远线 => -1, + _ => 0 + }; + var myPos = basePos.RotateAndExtend(Center, myRegion * 22.5f.DegToRad(), myExtend); + sa.DrawGuidance(myPos, 0, 10000, $"P5B1_二运_八方波动炮站位"); + } + + [ScriptMethod(name: "P5B1_二运_八方波动炮结束", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31603)$"], + userControl: Debugging)] + public void P5B1_二运_八方波动炮结束(Event ev, ScriptAccessory sa) + { + if (_parse != 5.2) return; + sa.Method.RemoveDraw("P5B1_二运_八方波动炮站位"); + _p5B.八方头标记录完毕.Reset(); + _parse = 5.21; + } + + [ScriptMethod(name: "P5B1_二运_塔收集", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["Operate:Add", "DataId:regex:^(201324[56])$"], + userControl: Debugging)] + public void P5B1_二运_塔收集(Event ev, ScriptAccessory sa) + { + if (_parse != 5.21) return; + const uint DOUBLE_TOWER = 2013246; + lock (_p5B.塔方位类型字典) + { + var isDoubleTower = ev.DataId() == DOUBLE_TOWER; + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(16, isDiagDiv: true); + _p5B.塔方位类型字典.TryAdd(region, isDoubleTower); + sa.DebugMsg($"P5B1_二运_塔收集:方位 {region} 的 {(isDoubleTower ? "双" : "单")}人塔", Debugging); + + if ((_p5B.塔方位类型字典.Count == 5 && _p5B.协作程序是远线) || (_p5B.塔方位类型字典.Count == 6 && !_p5B.协作程序是远线)) + _p5B.塔方位记录完毕.Set(); + } + } + + [ScriptMethod(name: "P5B1_二运_踩塔击退点", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:regex:^(201324[56])$"], + userControl: true, suppress: 1000)] + public void P5B1_二运_踩塔击退点(Event ev, ScriptAccessory sa) + { + if (_parse != 5.21) return; + _p5B.塔方位记录完毕.WaitOne(); + _p5B.玩家踩塔方位 = _p5B.FindPlayerTower(sa); + var basePos = new Vector3(100, 0, 102.5f); + var rad = _p5B.玩家踩塔方位 * 22.5f.DegToRad(); + var myKnockBackPos = basePos.RotateAndExtend(Center, rad); + sa.DrawGuidance(myKnockBackPos, 0, 10000, $"P5B1_二运_踩塔击退点"); + sa.DrawLine(Center, 0, 0, 10000, $"P5B1_二运_踩塔击退指引线", rad, 20f, 20f, isSafe: true); + + // 适配 BBY 的标点节奏,提前进入二运后半 + _parse = 5.25; + _pd.Init(sa, "P5二传"); + _pd.AddPriorities([0, 1, 2, 3, 4, 5, 6, 7]); // 依职能顺序添加优先值 + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "P5B1_二运_踩塔位置提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31534)$"], + userControl: true, suppress: 1000)] + public void P5B1_二运_踩塔位置提示(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + var text = _p5B.协作程序是远线 ? "站在场边将线拉长" : "站在塔中间"; + sa.Method.TextInfo(text, 3000); + } + + [ScriptMethod(name: "P5B1_二运_踩塔击退后删除绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31534)$"], + userControl: Debugging)] + public void P5B1_二运_踩塔击退后删除绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + sa.Method.RemoveDraw("P5B1_二运_踩塔.*"); + _p5B.塔方位记录完毕.Reset(); + } + + [ScriptMethod(name: "P5B1_二运_塔消失转阶段(废弃)", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["Operate:Remove", "DataId:regex:^(201324[56])$"], + userControl: Debugging, suppress: 1000)] + public void P5B1_二运_塔消失转阶段(Event ev, ScriptAccessory sa) + { + // if (_parse != 5.25) return; + // _parse = 5.25; + // _pd.Init(sa, "P5二传"); + // _pd.AddPriorities([0, 1, 2, 3, 4, 5, 6, 7]); // 依职能顺序添加优先值 + // sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "———————— 《P5B2 二传》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5B2_二传_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5B2_二传_获取转圈头标", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[1234679]|10)$"], + userControl: Debugging)] + public void P5B2_二传_获取转圈头标(Event ev, ScriptAccessory sa) + { + // 取攻击1234、锁链12、禁止12 + if (_parse != 5.25) return; + + lock (_pd) + { + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var targetJob = sa.GetPlayerJobByIndex(tidx); + sa.DebugMsg($"检测到{targetJob} 被标 ev.Id {mark}", Debugging); + _pd.AddActionCount(); + var pdVal = mark switch + { + 0x01 => 10, // 攻击1 + 0x02 => 20, // 攻击2 + 0x03 => 30, // 攻击3 + 0x04 => 40, // 攻击4 + 0x06 => 100, // 锁链1 + 0x07 => 110, // 锁链2 + 0x09 => 200, // 禁止1 + 0x10 => 210, // 禁止2 + _ => 0 + }; + _pd.AddPriority(tidx, pdVal); + if (_pd.ActionCount != 8) return; + sa.DebugMsg($"P5B2_二传_获取转圈头标:头标记录完毕", Debugging); + sa.DebugMsg($"{_pd.ShowPriorities()}", Debugging); + _p5B.转圈头标记录完毕.Set(); // 头标记录 + } + } + + [ScriptMethod(name: "P5B2_二传_获取女人位置与技能", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:7747", "SourceDataId:15720"], userControl: Debugging)] + public void P5B2_二传_获取女人位置与技能(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + _p5B.女人方位 = region; + _p5B.二传女人Id = ev.SourceId; + var obj = sa.GetById(ev.SourceId); + if (obj == null) return; + var transId = sa.GetTransformationId(obj); + sa.DebugMsg($"获得女人TransformationId为:{transId}", Debugging); + if (transId == null) return; + + const byte WOMAN_CROSS = 0, WOMAN_HOTWING = 4; + _p5B.女人是十字外安全 = transId != WOMAN_HOTWING; + + sa.DebugMsg($"P5B2_二传_获取女人位置与技能:{region} / 8,{(_p5B.女人是十字外安全 ? "十字外安全" : "辣翅内安全")}", Debugging); + _p5B.女人技能记录完毕.Set(); + } + + [ScriptMethod(name: "P5B2_二传_获取圆环旋转方向", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(009[CD])$"], userControl: Debugging)] + public void P5B2_二传_获取圆环旋转方向(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + const int ICON_ROTATE_CW = 156; // 009C + _p5B.圆环是顺时针 = ev.Id0() == ICON_ROTATE_CW; + sa.DebugMsg($"P5B2_二传_获取圆环旋转方向:{(_p5B.圆环是顺时针 ? "顺" : "逆")}时针", Debugging); + _p5B.圆环方向记录完毕.Set(); + } + + [ScriptMethod(name: "P5B2_二传_圆环直线与起跑位置", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(009[CD])$"], userControl: true)] + public void P5B2_二传_圆环直线与起跑位置(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + _p5B.圆环方向记录完毕.WaitOne(); + _p5B.女人技能记录完毕.WaitOne(); + _p5B.转圈头标记录完毕.WaitOne(); + + // 女人方位边缘,逆时针22.5deg,为禁1、禁2、攻1起跑点;中心镜像过去,是其余人起跑点 + var startPos = new Vector3(100, 0, 119.5f).RotateAndExtend(Center, + _p5B.女人方位 * 45f.DegToRad() + (_p5B.圆环是顺时针 ? 22.5f : -22.5f).DegToRad()); + + var myPriValRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + if (myPriValRank is 1 or 2 or 3 or 4 or 5) + startPos = startPos.PointCenterSymmetry(Center); + sa.DrawGuidance(startPos, 0, 10000, $"P5B2_二传_圆环直线与起跑位置_起跑位置"); + + var dp = sa.DrawRect(ev.SourceId, 0, 10000, $"P5B2_二传_圆环直线与起跑位置_圆环直线", 0, 12, 50); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + + [ScriptMethod(name: "P5B2_二传_获得圆环Id", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(31631)$"], userControl: Debugging)] + public void P5B2_二传_获得圆环Id(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + _p5B.圆环Id = ev.SourceId; + } + + [ScriptMethod(name: "P5B2_二传_圆环释放第一条", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31631)$", "TargetIndex:1"], userControl: Debugging)] + public void P5B2_二传_圆环释放第一条(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + + _p5B.圆环方向记录完毕.Reset(); + _p5B.女人技能记录完毕.Reset(); + _p5B.转圈头标记录完毕.Reset(); + + sa.Method.RemoveDraw($"P5B2_二传_圆环直线与起跑位置_圆环直线"); + sa.Method.RemoveDraw($"P5B2_二传_圆环直线与起跑位置_起跑位置"); + } + + [ScriptMethod(name: "P5B2_二传_圆环攻击绘图", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(31631)$", "TargetIndex:1"], userControl: true)] + public void P5B2_二传_圆环攻击绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + + for (int i = 0; i < 13; i++) + { + var rotation = _p5B.女人方位 * 45f.DegToRad() + (i + 1) * 9f.DegToRad() * (_p5B.圆环是顺时针 ? -1 : 1); + var dp = sa.DrawRect(Center, 0, 10000, $"P5B2_二传_圆环攻击绘图{i}", rotation, 10, 60, draw: false); + dp.Color = sa.Data.DefaultDangerColor.WithW(0.25f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp); + } + } + + + [ScriptMethod(name: "P5B2_二传_圆环攻击绘图删除", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(31632)$", "TargetIndex:1"], userControl: Debugging)] + public void P5B2_二传_圆环攻击绘图删除(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + sa.Method.RemoveDraw($"^(P5B2_二传_圆环攻击绘图{_p5B.圆环攻击次数})$"); + _p5B.圆环攻击次数++; + } + + [ScriptMethod(name: "P5B2_二传_女人攻击范围绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31631)$", "TargetIndex:1"], userControl: true)] + public void P5B2_二传_女人攻击范围绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + if (_p5B.女人是十字外安全) + { + var dp1 = sa.DrawRect(_p5B.二传女人Id, 0, 5500, $"P5B2_二传_女人攻击范围_女十字1", 0, 10, 60, draw: false); + var dp2 = sa.DrawRect(_p5B.二传女人Id, 0, 5500, $"P5B2_二传_女人攻击范围_女十字2", float.Pi / 2, 10, 60, draw: false); + dp1.Color = sa.Data.DefaultDangerColor.WithW(2.5f); + dp2.Color = sa.Data.DefaultDangerColor.WithW(2.5f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp2); + } + else + { + var dp3 = sa.DrawDonut(_p5B.二传女人Id, 0, 5500, $"P5B2_二传_女人攻击范围_女辣翅", 60, 8, draw: false); + dp3.Color = sa.Data.DefaultDangerColor.WithW(2.5f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.HotWing, dp3); + } + } + + [ScriptMethod(name: "P5B2_二传_穿入或停留提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31631)$", "TargetIndex:1"], userControl: true)] + public void P5B2_二传_穿入或停留提示(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + var text = _p5B.女人是十字外安全 ? "停停停" : "穿穿穿"; + sa.Method.TextInfo(text, 1500, true); + } + + [ScriptMethod(name: "*P5B2_二传_女人十字暂时移除大圆环", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31631)$", "TargetIndex:1"], userControl: true)] + public void P5B2_二传_女人十字暂时移除大圆环(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + if (!SpecialMode) return; + if (!_p5B.女人是十字外安全) return; + sa.DebugMsg($"女人是十字,暂时移除大圆环", Debugging); + sa.WriteVisible(sa.GetById(_p5B.圆环Id), false); + } + + [ScriptMethod(name: "P5B2_二传_女人释放技能", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3153[123])$", "TargetIndex:1"], userControl: Debugging, suppress: 1000)] + public void P5B2_二传_女人释放技能(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + _p5B.女人十字辣翅释放完毕.Set(); + sa.Method.RemoveDraw($"P5B2_二传_女人攻击范围.*"); + if (!_p5B.女人是十字外安全) return; + sa.Method.TextInfo("穿穿穿", 1500, true); + if (!SpecialMode) return; + sa.WriteVisible(sa.GetById(_p5B.圆环Id), true); + } + + [ScriptMethod(name: "P5B2_二传_指路", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3153[123])$", "TargetIndex:1"], userControl: true, suppress: 1000)] + public void P5B2_二传_指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + _p5B.女人十字辣翅释放完毕.WaitOne(); + + var myPriValRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + const int ATK1 = 0, ATK2 = 1, ATK3 = 2, ATK4 = 3; + const int BIND1 = 4, BIND2 = 5, STOP1 = 6, STOP2 = 7; + + var myBasePos = myPriValRank switch + { + ATK1 => new Vector3(80.5f, 0, 100f), + ATK2 => new Vector3(119.5f, 0, 100f), + ATK3 => new Vector3(98f, 0, 89f), + ATK4 => new Vector3(94.74f, 0, 81.74f), + BIND1 => new Vector3(110f, 0, 100f), + BIND2 => new Vector3(105.26f, 0, 81.74f), + STOP1 => new Vector3(111.34f, 0, 116.22f), + STOP2 => new Vector3(88.66f, 0, 116.22f), + }; + + if (!_p5B.圆环是顺时针 && myPriValRank is ATK1 or ATK2 or BIND1) + myBasePos = myBasePos.FoldPointHorizon(Center.X); + + var rotation = _p5B.女人方位 * 45f.DegToRad(); + var safePos = myBasePos.RotateAndExtend(Center, rotation); + sa.DrawGuidance(safePos, 0, 10000, $"P5B2_二传_指路"); + } + + [ScriptMethod(name: "P5A2_二传_处理结束", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(31625|33040)$"], + userControl: Debugging)] + public void P5A2_二传_处理结束(Event ev, ScriptAccessory sa) + { + if (_parse != 5.25) return; + sa.Method.RemoveDraw("P5B2_二传.*"); + _p5B.女人十字辣翅释放完毕.Reset(); + } + + #endregion 二运二传 + + #region P5C 三运三传 + + [ScriptMethod(name: "———————— 《P5C1 三运》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5C1_三运_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5C1_三运_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:32789"], + userControl: Debugging)] + public void P5C1_三运_分P(Event ev, ScriptAccessory sa) + { + _parse = 5.3; + + _p5B.Reset(sa); + _p5B.Dispose(); + _p5C.Register(); + + ResetSupportUnitVisibility(sa); + _pd.Init(sa, "P5三运"); + _pd.AddPriorities([0, 1, 2, 3, 4, 5, 6, 7]); // 依职能顺序添加优先值 + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "P5C1_三运_获取男女组合技", eventType: EventTypeEnum.PlayActionTimeline, + eventCondition: ["Id:7747", "SourceDataId:regex:^(15721|15722)$"], userControl: Debugging)] + public void P5C1_三运_获取男女组合技(Event ev, ScriptAccessory sa) + { + if (_parse != 5.3) return; + const uint OMEGA_MALE = 15721, OMEGA_FEMALE = 15722, OMEGA_BALD = 14669; + const byte MAN_CHARIOT = 0, MAN_DONUT = 4, WOMAN_CROSS = 0, WOMAN_HOTWING = 4; + + lock (_p5C.组合技记录) + { + if (_p5C.组合技记录.Count >= 6) return; + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(4); + IGameObject? obj = sa.GetById(ev.SourceId); + if (obj == null) return; + var transId = sa.GetTransformationId(obj); + var dataId = obj.DataId; + var isFirstRound = _p5C.组合技记录.Count < 2; + + var skillId = (dataId == OMEGA_FEMALE ? 8 : 0) + (transId == 4 ? 4 : 0) + region; + _p5C.组合技记录.TryAdd(region, (skillId, isFirstRound)); + sa.DebugMsg($"在方位 {region} 有第 {(isFirstRound ? "一" : "二")} 轮技能,技能ID {skillId}", Debugging); + if (_p5C.组合技记录.Count != 4) return; + _p5C.男女人组合技记录完毕.Set(); + } + } + + [ScriptMethod(name: "P5C1_三运_获取光头组合技和安全点", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(3164[34])$"], userControl: Debugging)] + public void P5C1_三运_获取光头组合技和安全点(Event ev, ScriptAccessory sa) + { + if (_parse != 5.3) return; + const uint FRONT_FIRST = 31643, SIDE_FIRST = 31644; + _p5C.男女人组合技记录完毕.WaitOne(); + lock (_p5C.组合技记录) + { + if (_p5C.组合技记录.Count >= 6) return; + // region已无所谓,随便取两个0~3以外的值 + var firstRoundSkillId = ev.ActionId == FRONT_FIRST ? 17 : 16; + var secondRoundSkillId = ev.ActionId == FRONT_FIRST ? 16 : 17; + _p5C.组合技记录.TryAdd(10, (firstRoundSkillId, true)); + _p5C.组合技记录.TryAdd(11, (secondRoundSkillId, false)); + sa.DebugMsg($"光头的第一轮技能Id:{firstRoundSkillId}", Debugging); + + if (_p5C.组合技记录.Count != 6) return; + _p5C.FindComboAttackSafePoint(sa); + sa.DebugMsg($"组合技安全区地点为:{_p5C.组合技安全区[0]}, {_p5C.组合技安全区[1]}", Debugging); + + _p5C.男女人组合技记录完毕.Reset(); + _p5C.组合技安全区记录完毕.Set(); + } + } + + [ScriptMethod(name: "*P5C1_三运_出现的男女人与光头缩小", eventType: EventTypeEnum.PlayActionTimeline, + eventCondition: ["Id:7747", "SourceDataId:regex:^(15721|15722|14669)$"], userControl: true)] + public void P5C1_三运_出现的男女人与光头缩小(Event ev, ScriptAccessory sa) + { + if (_parse != 5.3) return; + if (!SpecialMode) return; + var obj = sa.GetById(ev.SourceId); + sa.ScaleModify(obj, 0.4f); + } + + [ScriptMethod(name: "P5C1_三运_组合技攻击范围绘图", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(3164[34])$"], userControl: true)] + public void P5C1_三运_组合技攻击范围绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 5.3) return; + _p5C.组合技安全区记录完毕.WaitOne(); + + foreach (var (region, (skillId, isFirstRound)) in _p5C.组合技记录) + DrawComboSkillAttackRange(sa, region, skillId, isFirstRound, "第一轮"); + + _p5C.第一段组合技结束.WaitOne(); + sa.Method.RemoveDraw($"P5C1_三运_组合技攻击范围绘图_第一轮.*"); + + foreach (var (region, (skillId, isFirstRound)) in _p5C.组合技记录) + DrawComboSkillAttackRange(sa, region, skillId, !isFirstRound, "第二轮"); + + return; + + void DrawComboSkillAttackRange(ScriptAccessory sa, int region, int skillId, bool draw, string prefix) + { + if (!draw) return; + var skillType = skillId / 4; + + Vector3 ownerPos; + float rotation; + if (region < 4) + { + ownerPos = new Vector3(100, 0, 110).RotateAndExtend(Center, (45f + 90f * region).DegToRad()); + rotation = (225f + 90f * region).DegToRad(); + } + else + { + ownerPos = new Vector3(100, 0, 100); + rotation = skillId == 16 ? 90f.DegToRad() : 0f; + } + + // skillType + const int CHARIOT = 0, DONUT = 1, CROSS = 2, HOTWING = 3, FAN = 4; + switch (skillType) + { + case DONUT: + sa.DrawDonut(ownerPos, 0, 12000, $"P5C1_三运_组合技攻击范围绘图_{prefix}_男月环", 40, 10); + break; + case CHARIOT: + sa.DrawCircle(ownerPos, 0, 12000, $"P5C1_三运_组合技攻击范围绘图_{prefix}_男钢铁", 10); + break; + case CROSS: + var dp1 = sa.DrawRect(ownerPos, 0, 12000, $"P5C1_三运_组合技攻击范围绘图_{prefix}_女十字1", rotation, 10, 60, draw: false); + var dp2 = sa.DrawRect(ownerPos, 0, 12000, $"P5C1_三运_组合技攻击范围绘图_{prefix}_女十字2", rotation + float.Pi / 2, 10, 60, draw: false); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Straight, dp2); + break; + case HOTWING: + var dp3 = sa.DrawDonut(ownerPos, 0, 12000, $"P5C1_三运_组合技攻击范围绘图_{prefix}_女辣翅", 60, 8, draw: false); + dp3.Rotation = rotation; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.HotWing, dp3); + break; + case FAN: + sa.DrawFan(ownerPos, 0, 12000, $"P5C1_三运_组合技攻击范围绘图_{prefix}_光头刀1", 120f.DegToRad(), rotation, 20f, 0f); + sa.DrawFan(ownerPos, 0, 12000, $"P5C1_三运_组合技攻击范围绘图_{prefix}_光头刀2", 120f.DegToRad(), rotation + 180f.DegToRad(), 20f, 0f); + break; + } + } + } + + [ScriptMethod(name: "P5C1_三运_安全点指路", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(3164[34])$"], userControl: true)] + public void P5C1_三运_安全点指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.3) return; + _p5C.组合技安全区记录完毕.WaitOne(); + var (safePos1, hintString1) = GetSafePosOfComboSkill(_p5C.组合技安全区[0]); + var (safePos2, hintString2) = GetSafePosOfComboSkill(_p5C.组合技安全区[1]); + sa.Method.TextInfo($"{hintString1} -> {hintString2}", 4500, true); + + sa.DrawGuidance(safePos1, 0, 12000, $"P5C1_三运_安全点指路1"); + sa.DrawGuidance(safePos1, safePos2, 0, 12000, $"P5C1_三运_安全点指路1预备", isSafe: false); + + _p5C.第一段组合技结束.WaitOne(); + sa.Method.RemoveDraw($"P5C1_三运_安全点指路1.*"); + sa.DrawGuidance(safePos2, 0, 12000, $"P5C1_三运_安全点指路2"); + + (Vector3, string) GetSafePosOfComboSkill(int safePosIdx) + { + var rotation = safePosIdx % 4 * 90f.DegToRad(); + var distance = (safePosIdx / 4) switch + { + 0 => 4.5f, + 1 => 12f, + 2 => 19f, + _ => 0, + }; + + var markHint = (safePosIdx % 4) switch + { + 0 => "C", + 1 => "B", + 2 => "A", + _ => "D", + }; + + var distanceHint = (safePosIdx / 4) switch + { + 0 => "近", + 1 => "中", + _ => "远", + }; + + var hintString = $"{markHint}{distanceHint}"; + return (new Vector3(100, 0, 100 + distance).RotateAndExtend(Center, rotation), hintString); + } + } + + [ScriptMethod(name: "P5C1_三运_第一段组合技结束", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3152[56])$"], userControl: Debugging)] + public void P5C1_三运_第一段组合技结束(Event ev, ScriptAccessory sa) + { + if (_parse != 5.3) return; + _p5C.第一段组合技结束.Set(); + } + + [ScriptMethod(name: "P5C1_三运_前半运动会组合技结束", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3160[78])$"], userControl: Debugging)] + public void P5C1_三运_前半运动会组合技结束(Event ev, ScriptAccessory sa) + { + if (_parse != 5.3) return; + sa.Method.RemoveDraw($"P5C1_三运.*"); + _p5C.第一段组合技结束.Reset(); + _p5C.组合技安全区记录完毕.Reset(); + _parse = 5.35; + } + + [ScriptMethod(name: "———————— 《P5C2 三传》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5C2_三传_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5C2_三传_分P", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void P5B2_三传_分P(Event ev, ScriptAccessory sa) + { + _parse = 5.35; + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "P5C2_三传_获取三传头标", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[1234679]|10)$"], + userControl: Debugging)] + public void P5C2_三传_获取三传头标(Event ev, ScriptAccessory sa) + { + // 取攻击1234、锁链12、禁止12 + if (_parse != 5.35) return; + + lock (_pd) + { + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var targetJob = sa.GetPlayerJobByIndex(tidx); + sa.DebugMsg($"检测到{targetJob} 被标 ev.Id {mark}", Debugging); + _pd.AddActionCount(); + var pdVal = mark switch + { + 0x01 => 10, // 攻击1 + 0x02 => 20, // 攻击2 + 0x03 => 30, // 攻击3 + 0x04 => 40, // 攻击4 + 0x06 => 100, // 锁链1 + 0x07 => 110, // 锁链2 + 0x09 => 200, // 禁止1 + 0x10 => 210, // 禁止2 + _ => 0 + }; + _pd.AddPriority(tidx, pdVal); + if (_pd.ActionCount != 8) return; + sa.DebugMsg($"P5C2_三传_获取三传头标:头标记录完毕", Debugging); + sa.DebugMsg($"{_pd.ShowPriorities()}", Debugging); + _p5C.三传头标记录完毕.Set(); // 头标记录 + } + } + + [ScriptMethod(name: "P5C2_三传_光头扫描范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3163[89])$"], + userControl: true)] + public void P5C2_三传_光头扫描范围(Event ev, ScriptAccessory sa) + { + if (_parse != 5.35) return; + _p5C.三传头标记录完毕.WaitOne(); + var myPriValRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + var isSafe = myPriValRank >= 6; + const uint CANNON_LEFT = 31639, CANNON_RIGHT = 31638; + var rotation = (ev.ActionId == CANNON_RIGHT ? 90f : -90f).DegToRad(); + sa.DrawFan(Center, 0, 10000, $"P5C2_三传_光头扫描范围", float.Pi, rotation, 40, 0, isSafe); + } + + [ScriptMethod(name: "P5C2_三传_指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3163[89])$"], + userControl: true)] + public void P5C2_三传_指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.35) return; + _p5C.三传头标记录完毕.WaitOne(); + + var myPriValRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + const int ATK1 = 0, ATK2 = 1, ATK3 = 2, ATK4 = 3; + const int BIND1 = 4, BIND2 = 5, STOP1 = 6, STOP2 = 7; + + // 以欧米茄打上侧为准 + var safePos = myPriValRank switch + { + ATK1 => new Vector3(80.5f, 0, 100.5f), + ATK2 => new Vector3(119.5f, 0, 100.5f), + ATK3 => new Vector3(102f, 0, 111f), + ATK4 => new Vector3(105.26f, 0f, 118.26f), + BIND1 => new Vector3(90f, 0f, 100.5f), + BIND2 => new Vector3(94.74f, 0f, 118.26f), + STOP1 => new Vector3(90.8f, 0f, 90.8f), + STOP2 => new Vector3(109.2f, 0f, 90.8f), + }; + + const uint CANNON_LEFT = 31639, CANNON_RIGHT = 31638; + var rotation = (ev.ActionId == CANNON_LEFT ? 90f : -90f).DegToRad(); + safePos = safePos.RotateAndExtend(Center, rotation); + sa.DrawGuidance(safePos, 0, 10000, $"P5C2_三传_指路"); + } + + [ScriptMethod(name: "P5C2_三传_欧米茄扫描结束三传结束", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3163[89])$"], + userControl: Debugging)] + public void P5C3_三传_欧米茄扫描结束三传结束(Event ev, ScriptAccessory sa) + { + if (_parse != 5.35) return; + _p5C.三传头标记录完毕.Reset(); + sa.Method.RemoveDraw($"P5C2_三传.*"); + _pd.Init(sa, "P5四传"); + _parse = 5.38; + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "———————— 《P5C3 四传》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P5C3_四传_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P5C3_四传_获取四传头标", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[1234679]|10)$"], + userControl: Debugging)] + public void P5C3_四传_获取四传头标(Event ev, ScriptAccessory sa) + { + // 取攻击1234、锁链12、禁止12 + if (_parse != 5.38) return; + + lock (_pd) + { + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var targetJob = sa.GetPlayerJobByIndex(tidx); + sa.DebugMsg($"检测到{targetJob} 被标 ev.Id {mark}", Debugging); + _pd.AddActionCount(); + var pdVal = mark switch + { + 0x01 => 10, // 攻击1 + 0x02 => 20, // 攻击2 + 0x03 => 30, // 攻击3 + 0x04 => 40, // 攻击4 + 0x06 => 100, // 锁链1 + 0x07 => 110, // 锁链2 + 0x09 => 200, // 禁止1 + 0x10 => 210, // 禁止2 + _ => 0 + }; + _pd.AddPriority(tidx, pdVal); + if (_pd.ActionCount != 8) return; + sa.DebugMsg($"P5C3_四传_获取四传头标:头标记录完毕", Debugging); + sa.DebugMsg($"{_pd.ShowPriorities()}", Debugging); + _p5C.四传头标记录完毕.Set(); // 头标记录 + } + } + + [ScriptMethod(name: "P5C3_四传_蟑螂方位记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:32374"], userControl: Debugging)] + public void P5C3_四传_蟑螂方位记录(Event ev, ScriptAccessory sa) + { + if (_parse != 5.38) return; + + _p5C.蟑螂Id = ev.SourceId; + _p5C.蟑螂方位 = ev.SourcePosition.GetRadian(Center).RadianToRegion(4, isDiagDiv: true); + sa.DebugMsg($"P5C3_四传_蟑螂方位记录:{_p5C.蟑螂方位} / 4", Debugging); + _p5C.蟑螂方位记录完毕.Set(); + } + + [ScriptMethod(name: "P5C3_四传_指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:32374"], userControl: true)] + public void P5C3_四传_指路(Event ev, ScriptAccessory sa) + { + if (_parse != 5.38) return; + + _p5C.蟑螂方位记录完毕.WaitOne(); + _p5C.四传头标记录完毕.WaitOne(); + + var myPriValRank = _pd.FindPriorityIndexOfKey(sa.GetMyIndex()); + const int ATK1 = 0, ATK2 = 1, ATK3 = 2, ATK4 = 3; + const int BIND1 = 4, BIND2 = 5, STOP1 = 6, STOP2 = 7; + + // 以蟑螂方位在南为准 + var myBasePos = myPriValRank switch + { + ATK1 => new Vector3(119.5f, 0, 100f), + ATK2 => new Vector3(80.5f, 0, 100f), + ATK3 => new Vector3(98f, 0, 89f), + ATK4 => new Vector3(94.74f, 0f, 81.74f), + BIND1 => new Vector3(110f, 0f, 100f), + BIND2 => new Vector3(105.26f, 0f, 81.74f), + STOP1 => new Vector3(110.3f, 0f, 116.5f), + STOP2 => new Vector3(89.7f, 0f, 116.5f), + }; + + var safePos = myBasePos.RotateAndExtend(Center, _p5C.蟑螂方位 * 90f.DegToRad()); + sa.DrawGuidance(safePos, 0, 10000, $"P5C3_四传_指路"); + } + + [ScriptMethod(name: "P5C3_四传_结束", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:32374"], + userControl: Debugging)] + public void P5C3_四传_结束(Event ev, ScriptAccessory sa) + { + if (_parse != 5.38) return; + _parse = 5.39; + _p5C.蟑螂方位记录完毕.Reset(); + _p5C.四传头标记录完毕.Reset(); + _p5C.Reset(sa); + _p5C.Dispose(); + sa.Method.RemoveDraw($"P5.*"); + } + + [ScriptMethod(name: "P5C3_四传_接线范围", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0059"], + userControl: true)] + public void P5C3_四传_接线范围(Event ev, ScriptAccessory sa) + { + if (_parse != 5.38) return; + + _p5C.蟑螂方位记录完毕.WaitOne(); + _p5C.四传头标记录完毕.WaitOne(); + + if (_p5C.扫描接线开启) return; + _p5C.扫描接线开启 = true; + + // 当且仅当本轮正确的玩家接线,才会绘制大圈 + _p5C.扫描接线Framework = sa.Method.RegistFrameworkUpdateAction(Action); + + const uint TETHER_ID = 0x59; + return; + + void Action() + { + var bossObj = sa.GetById(_p5C.蟑螂Id); + if (bossObj is null) return; + + foreach (var member in sa.Data.PartyList) + { + // 找到小队成员 + IGameObject? memberObj = sa.GetById(member); + if (memberObj is null) continue; + int memberIdx = sa.GetPlayerIdIndex(member); + + void CleanUp() + { + _p5C.接线绘图字典.Remove(memberIdx, out _); + sa.Method.RemoveDraw($"P5C_四传_接线范围_线源{memberIdx}"); + } + + // 获取优先级,禁止头标优先值 >=200 + if (!_pd.Priorities.TryGetValue(memberIdx, out int memberPrival) || memberPrival < 200) { CleanUp(); continue; } + + // 线源追溯 + var tetherSource = sa.GetTetherSource((IBattleChara?)memberObj, TETHER_ID); + bool isCorrectTether = tetherSource.Count == 1 && tetherSource[0] == _p5C.蟑螂Id; + if (!isCorrectTether) { CleanUp(); continue; } + + // 避免反复绘图 + if (_p5C.接线绘图字典.TryAdd(memberIdx, true)) + sa.DrawCircle(member, 0, Int32.MaxValue, $"P5C3_四传_接线范围_线源{memberIdx}", 15); + } + } + } + + #endregion 三运三传 + + #region P6 宇宙记忆 + + [ScriptMethod(name: "———————— 《P6 宇宙记忆》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P6_普通攻击_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P6_宇宙记忆_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31649"], + userControl: Debugging)] + public void P6_宇宙记忆_分P(Event ev, ScriptAccessory sa) + { + _parse = 6; + _p6.Reset(sa); + _p6.Register(); + _p6.BossId = ev.SourceId; + ResetSupportUnitVisibility(sa); + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "P6_普通攻击绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31649", "TargetIndex:1"], + userControl: true)] + public void P6_普通攻击绘图(Event ev, ScriptAccessory sa) + { + if (_parse != 6) return; + _p6.使能普通攻击绘图 = true; + _p6.普通攻击绘图Framework = sa.Method.RegistFrameworkUpdateAction(Action); + return; + + void Action() + { + if (!_p6.使能普通攻击绘图) + return; + + var myIndex = sa.GetMyIndex(); + UpdateFarthestPlayer(); + UpdateMainTank(); + return; + + void UpdateFarthestPlayer() + { + int farthestIdx = FindFarthestPlayerIndex(); + if (farthestIdx == _p6.最远玩家Idx) return; + + const string hint = "最远"; + sa.Method.RemoveDraw($"P6_普通攻击绘图_{hint}_{_p6.最远玩家Idx}"); + _p6.最远玩家Idx = farthestIdx; + + DrawPlayerCircle(farthestIdx, hint, farthestIdx == 1 && myIndex == 1); + + if (farthestIdx != 1 && myIndex == 1 && !_p6.已绘制远离指示线) + { + _p6.已绘制远离指示线 = true; + var dp = sa.DrawGuidance(Center, 0, Int32.MaxValue, $"P6_普通攻击绘图_远离指示线", + 180f.DegToRad(), draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + sa.Method.TextInfo("退到最远!", 1500, true); + } + else if (farthestIdx == 1 && myIndex == 1 && _p6.已绘制远离指示线) + { + _p6.已绘制远离指示线 = false; + sa.Method.RemoveDraw($"P6_普通攻击绘图_远离指示线"); + } + } + + int FindFarthestPlayerIndex() + { + float maxDistance = 0f; + int farthestIdx = 0; + + foreach (uint memberId in sa.Data.PartyList) + { + var memberObj = sa.GetById(memberId); + if (memberObj is null) continue; + + float distance = Vector3.Distance(memberObj.Position, Center); + if (distance < maxDistance) continue; + maxDistance = distance; + farthestIdx = sa.GetPlayerIdIndex(memberId); + } + return farthestIdx; + } + + void UpdateMainTank() + { + int mainTankIdx = sa.GetPlayerIdIndex((uint)sa.Data.EnmityList[_p6.BossId][0]); + if (mainTankIdx == _p6.一仇玩家Idx) return; + const string hint = "一仇"; + sa.Method.RemoveDraw($"P6_普通攻击绘图_{hint}_{_p6.一仇玩家Idx}"); + _p6.一仇玩家Idx = mainTankIdx; + + DrawPlayerCircle(mainTankIdx, hint, mainTankIdx == 0 && myIndex == 0); + + if (mainTankIdx != 0 && myIndex == 0 && !_p6.已提示建立一仇) + { + _p6.已提示建立一仇 = true; + sa.Method.TextInfo("建立一仇!", 1500, true); + } + else if (mainTankIdx == 0 && myIndex == 0 && _p6.已提示建立一仇) + { + _p6.已提示建立一仇 = false; + } + } + + void DrawPlayerCircle(int playerIdx, string hint, bool isCorrectChara) + { + uint playerId = sa.Data.PartyList[playerIdx]; + + var dp = sa.DrawCircle(playerId, 0, Int32.MaxValue, $"P6_普通攻击绘图_{hint}_{playerIdx}", + 5f, isCorrectChara, draw: false); + + dp.Color = dp.Color.WithW(2f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + } + + [ScriptMethod(name: "P6_宇宙龙炎_绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31654"], + userControl: true)] + public void P6_宇宙龙炎_绘图(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.宇宙龙炎绘图开启.WaitOne(); + + var sid = ev.SourceId; + var myIndex = sa.GetMyIndex(); + var isTank = myIndex <= 1; + var dp1 = sa.DrawDonut(sid, 0, 10000, $"P6_宇宙龙炎_绘图1", 8f, 7.7f, isTank, draw: false); + dp1.Color = isTank ? sa.Data.DefaultSafeColor.WithW(5f) : sa.Data.DefaultDangerColor.WithW(5f); + dp1.SetOwnersDistanceOrder(true, 1); + var dp2 = sa.DrawDonut(sid, 0, 10000, $"P6_宇宙龙炎_绘图2", 8f, 7.7f, isTank, draw: false); + dp2.Color = isTank ? sa.Data.DefaultSafeColor.WithW(5f) : sa.Data.DefaultDangerColor.WithW(5f); + dp2.SetOwnersDistanceOrder(true, 2); + var dp3 = sa.DrawDonut(sid, 0, 10000, $"P6_宇宙龙炎_绘图3", 6f, 5.7f, !isTank, draw: false); + dp3.Color = !isTank ? sa.Data.DefaultSafeColor.WithW(5f) : sa.Data.DefaultDangerColor.WithW(5f); + dp3.SetOwnersDistanceOrder(true, 3); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp2); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp3); + } + + [ScriptMethod(name: "P6_宇宙龙炎_绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31655"], + userControl: Debugging, suppress: 500)] + public void P6A_宇宙龙炎_绘图删除(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.Method.RemoveDraw($"P6.*"); + + switch (_parse) + { + case 6.1: + _p6.ResetCosmoArrow(sa); + break; + case 6.2: + _p6.ResetUnlimitedWaveCannon(); + _p6.ResetSpreadWaveCannon(); + break; + } + + _p6.使能普通攻击绘图 = true; + _p6.宇宙龙炎绘图开启.Reset(); + } + + [ScriptMethod(name: "P6_波动炮_分摊位置绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31657"], + userControl: true)] + public void P6_波动炮_分摊位置绘图(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.波动炮分摊绘图开启.WaitOne(); + var isTank = sa.GetMyIndex() <= 1; + var stackPos = new Vector3(100, 0, isTank ? 103 : 109); + sa.DrawGuidance(stackPos, 0, 10000, $"P6_波动炮_分摊位置绘图"); + } + + [ScriptMethod(name: "P6_波动炮_八方攻击计数", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31659"], + userControl: Debugging, suppress: 500)] + public void P6_波动炮_八方攻击计数(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.波动炮判定次数++; + if (_p6.波动炮判定次数 < 2) return; + sa.Method.RemoveDraw($"P6_波动炮_八方指路.*"); + sa.Method.RemoveDraw($"P6_波动炮_指引线.*"); + _p6.波动炮分摊绘图开启.Set(); + } + + [ScriptMethod(name: "P6_波动炮_分摊绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31658"], + userControl: Debugging, suppress: 500)] + public void P6_波动炮_分摊绘图删除(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.Method.RemoveDraw($".*"); + switch (_parse) + { + case 6.1: + _p6.ResetUnlimitedWaveCannon(); + _p6.ResetSpreadWaveCannon(); + break; + case 6.2: + _p6.ResetCosmoArrow(sa); + break; + } + _p6.使能普通攻击绘图 = true; + _p6.波动炮八方绘图开启.Reset(); + _p6.波动炮分摊绘图开启.Reset(); + } + + #endregion 宇宙记忆 + + #region P6A 宇宙天箭 + + [ScriptMethod(name: "———————— 《P6A 宇宙天箭》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P6A_宇宙天箭_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P6A_宇宙天箭_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31650"], + userControl: Debugging)] + public void P6A_宇宙天箭_分P(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _parse = _parse == 6.1 ? 6.2 : 6.1; + sa.DebugMsg($"当前阶段为:{_parse} 宇宙天箭", Debugging); + _p6.ResetAutoAttack(sa, false); + _p6.宇宙天箭读条开始.Set(); + } + + [ScriptMethod(name: "P6A_宇宙天箭_类型判断", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31651"], + userControl: Debugging, suppress: 1000)] + public void P6A_宇宙天箭_类型判断(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + + _p6.宇宙天箭读条开始.WaitOne(); + if (_p6.宇宙天箭类型判断完毕) return; + + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + _p6.宇宙天箭是内天箭 = region % 2 == 0; + _p6.宇宙天箭类型判断完毕 = true; + sa.DebugMsg($"P6A_宇宙天箭_类型判断:阶段 {_parse} 的宇宙天箭,类型为 {(_p6.宇宙天箭是内天箭 ? "内" : "外")} 天箭", Debugging); + } + + [ScriptMethod(name: "P6A_宇宙天箭_读条完毕", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31650"], + userControl: Debugging)] + public void P6A_宇宙天箭_读条完毕(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.宇宙天箭读条开始.Reset(); + } + + [ScriptMethod(name: "*P6A_宇宙天箭_屏蔽特效", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(3165[12])$", "TargetIndex:1"], userControl: true)] + public void P6A_宇宙天箭_屏蔽特效(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + if (!SpecialMode) return; + if (_p6.宇宙天箭判定次数 > 4) return; + sa.WriteVisible(sa.GetById(ev.SourceId), false); + } + + [ScriptMethod(name: "P6A_宇宙天箭_判定次数增加", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(3165[12])$"], + userControl: Debugging, suppress: 500)] + public void P6A_宇宙天箭_判定次数增加(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.宇宙天箭判定次数++; + switch (_parse) + { + case 6.1 when _p6.宇宙天箭判定次数 == 7: + _p6.宇宙龙炎绘图开启.Set(); + if (!SpecialMode) return; + ResetSupportUnitVisibility(sa); + break; + case 6.2 when _p6.宇宙天箭判定次数 == 5: + _p6.波动炮八方绘图开启.Set(); + if (!SpecialMode) return; + ResetSupportUnitVisibility(sa); + break; + } + } + + [ScriptMethod(name: "P6A_宇宙天箭_绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31650"], + userControl: true)] + public void P6A_宇宙天箭_绘图(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.DebugMsg($"P6A_宇宙天箭_绘图:启动宇宙天箭绘图Framework", Debugging); + _p6.宇宙天箭绘图Framework = sa.Method.RegistFrameworkUpdateAction(Action); + return; + + void Action() + { + int[] 内天箭图案 = [0, 12, 60, 195, 3, 12, 48, 192]; + int[] 外天箭图案 = [0, 12, 15, 51, 204, 48, 192]; + if (!_p6.宇宙天箭类型判断完毕) return; + var cosmoArrowPattern = _p6.宇宙天箭是内天箭 ? 内天箭图案 : 外天箭图案; + if (_p6.宇宙天箭绘图图案序号 == _p6.宇宙天箭判定次数) return; + _p6.宇宙天箭绘图图案序号 = _p6.宇宙天箭判定次数; + + var pattern = _p6.宇宙天箭绘图图案序号 >= cosmoArrowPattern.Length ? 0 : cosmoArrowPattern[_p6.宇宙天箭绘图图案序号]; + DrawCosmoArrowPattern(pattern); + + sa.DebugMsg($"P6A_宇宙天箭_绘图:绘制宇宙天箭绘图图案序号 {_p6.宇宙天箭绘图图案序号} ({pattern})", Debugging); + return; + + void DrawCosmoArrowPattern(int pt) + { + for (int i = 0; i < 8; i++) + { + if (((pt >> i) & 1) == 1) + { + // 计算中心位置 + float biasZ = (2.5f + (i / 2) * 5) * (i % 2 == 0 ? -1 : 1); + sa.DrawRect(new Vector3(80f, 0, 100f + biasZ), 0, 20000, + $"P6A_宇宙天箭_绘图_{i}", 90f.DegToRad(), 5f, 60f); + sa.DrawRect(new Vector3(100f + biasZ, 0, 80f), 0, 20000, + $"P6A_宇宙天箭_绘图_{i}", 0, 5f, 60f); + } + else + sa.Method.RemoveDraw($"P6A_宇宙天箭_绘图_{i}"); + } + } + } + } + + [ScriptMethod(name: "P6A_宇宙天箭_指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31650"], + userControl: true)] + public void P6A_宇宙天箭_指路(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.DebugMsg($"P6A_宇宙天箭_指路:启动宇宙天箭指路Framework", Debugging); + _p6.宇宙天箭指路Framework = sa.Method.RegistFrameworkUpdateAction(Action); + return; + + void Action() + { + // 以右下为基准 + int[][] 天箭指路图案 = + [ + [33, 11, 11, 55, 55, 55, 33], // 内天箭指路图案DPS + [33, 55, 55, 33, 55, 33, 33], // 外天箭指路图案DPS + [33, 11, 11, 33, 53, 88, 38], // 内天箭指路图案TN + [33, 55, 55, 33, 88, 38, 38] // 外天箭指路图案TN + ]; + + if (!_p6.宇宙天箭类型判断完毕) return; + int cosmoArrowGuidanceIdx, myRotation; + int myIndex = sa.GetMyIndex(); + bool isFirstCosmoArrow = _parse == 6.1; + if (isFirstCosmoArrow) + { + (cosmoArrowGuidanceIdx, myRotation) = myIndex switch + { + 0 => (2, 2), + 1 => (0, 0), + _ => (0, 3) + }; + } + else + { + (cosmoArrowGuidanceIdx, myRotation) = myIndex switch + { + 0 => (2, 2), // MT左上正上 + 1 => (2, 1), // ST右上正右 + 2 => (2, 3), // H1左下正左 + 3 => (2, 0), // H2右下正下 + 4 => (0, 3), // D1左下 + 5 => (0, 0), // D2右下 + 6 => (0, 2), // D3左上 + 7 => (0, 1) // D4右上 + }; + } + cosmoArrowGuidanceIdx += _p6.宇宙天箭是内天箭 ? 0 : 1; + var cosmoArrowGuidance = 天箭指路图案[cosmoArrowGuidanceIdx]; + if (_p6.宇宙天箭指路图案序号 == _p6.宇宙天箭判定次数) return; + _p6.宇宙天箭指路图案序号 = _p6.宇宙天箭判定次数; + + var guidance = _p6.宇宙天箭指路图案序号 >= cosmoArrowGuidance.Length ? 0 : cosmoArrowGuidance[_p6.宇宙天箭指路图案序号]; + var nextGuidance = _p6.宇宙天箭指路图案序号 + 1 >= cosmoArrowGuidance.Length ? 0 : cosmoArrowGuidance[_p6.宇宙天箭指路图案序号 + 1]; + DrawCosmoArrowGuidance(guidance, nextGuidance, myRotation); + sa.Method.RemoveDraw($"P6A_宇宙天箭_指路{_p6.宇宙天箭指路图案序号 - 1}"); + sa.DebugMsg($"P6A_宇宙天箭_指路:绘制宇宙天箭指路图案序号 {_p6.宇宙天箭指路图案序号} {guidance} {myRotation} {nextGuidance}", Debugging); + return; + + void DrawCosmoArrowGuidance(int gd, int nextGd, int rot) + { + if (gd == 0) return; + var guidancePos = GetGuidancePos(gd, rot); + if (nextGd != 0) + { + var nextGuidancePos = GetGuidancePos(nextGd, rot); + sa.DrawGuidance(guidancePos, nextGuidancePos, 0, 20000, $"P6A_宇宙天箭_指路{_p6.宇宙天箭指路图案序号}", isSafe: false); + } + sa.DrawGuidance(guidancePos, 0, 20000, $"P6A_宇宙天箭_指路{_p6.宇宙天箭指路图案序号}", isSafe: true); + } + + Vector3 GetGuidancePos(int gd, int rot) + { + var (digitX, digitZ) = (gd.GetDecimalDigit(1), gd.GetDecimalDigit(2)); + var biasX = digitX == 8 ? 0f : (2.5f + (digitX / 2) * 5) * (digitX % 2 == 0 ? -1 : 1); + var biasZ = digitZ == 8 ? 13f : (2.5f + (digitZ / 2) * 5) * (digitZ % 2 == 0 ? -1 : 1); + Vector3 guidancePos = new Vector3(100 + biasX, 0, 100 + biasZ).RotateAndExtend(Center, rot * 90f.DegToRad()); + return guidancePos; + } + } + } + + #endregion 宇宙天箭 + + #region P6B 解限波动炮 + + [ScriptMethod(name: "———————— 《P6B 解限波动炮》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P6B_解限波动炮_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P6B_解限波动炮_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31660"], + userControl: Debugging)] + public void P6B_解限波动炮_分P(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.DebugMsg($"当前阶段为:{_parse} 解限波动炮", Debugging); + _p6.ResetAutoAttack(sa, false); + } + + [ScriptMethod(name: "P6B_解限波动炮_指路场中", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31660"], + userControl: true)] + public void P6B_解限波动炮_指路场中(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.DrawGuidance(Center, 0, 10000, $"P6A_解限波动炮_指路场中"); + } + + [ScriptMethod(name: "P6B_解限波动炮_计算跑动方向", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31661"], + userControl: Debugging)] + public void P6B_解限波动炮_计算跑动方向(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(8, isDiagDiv: true); + if (_p6.解限波动炮第一炮方位 == -1) + { + _p6.解限波动炮第一炮方位 = region; + } + else if (!_p6.解限波动炮方向判断完毕) + { + var diff = _p6.解限波动炮第一炮方位 - region; + _p6.解限波动炮是顺时针 = diff is 1 or -7; + _p6.解限波动炮方向判断完毕 = true; + _p6.解限波动炮跑动方向绘图.Set(); + } + } + + [ScriptMethod(name: "P6B_解限波动炮_跑动方向绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31660"], + userControl: true)] + public void P6B_解限波动炮_跑动方向绘图(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.解限波动炮跑动方向绘图.WaitOne(); + DrawCannonRoute(false); + _p6.脚下出现黄圈.WaitOne(); + DrawCannonRoute(true); + if (_parse == 6.1) // 跟八方波动炮 + { + _p6.波动炮八方绘图开启.WaitOne(); + sa.Method.RemoveDraw($"P6B_解限波动炮_跑动方向绘图.*"); + + List partySpreadRegion = [4, 2, 6, 0, 7, 1, 5, 3]; + var pos = new Vector3(100, 0, 114f).RotateAndExtend(Center, partySpreadRegion[sa.GetMyIndex()] * 45f.DegToRad()); + sa.DrawGuidance(pos, 0, 20000, $"P6_波动炮_八方指路"); + for (int i = 0; i < 8; i++) + { + var dp = sa.DrawLine(Center, 0, 0, 20000, $"P6_波动炮_指引线{i}", partySpreadRegion[i] * 45f.DegToRad(), 40f, 20f, draw: false); + dp.Color = i switch + { + 0 or 1 => new Vector4(0.1f, 0.1f, 1, 1), + 2 or 3 => new Vector4(0.1f, 1f, 0.1f, 1), + _ => new Vector4(1, 0.1f, 0.1f, 1), + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + } + + void DrawCannonRoute(bool isSafe) + { + var isCw = _p6.解限波动炮是顺时针; + var startRegion = (_p6.解限波动炮第一炮方位 + (isCw ? 1 : -1) + 8) % 8; + var startDonutRad = (_p6.解限波动炮第一炮方位 * 45f).DegToRad(); + var startLineRad = (startRegion * 45f).DegToRad() + (isCw ? 22.5f : -22.5f).DegToRad(); + + sa.DebugMsg($"开始画波动炮路径,isCW {isCw},startRegion {startRegion},startDonutRad {startDonutRad.RadToDeg()},startLineRad {startLineRad.RadToDeg()}", Debugging); + + var dp1 = sa.DrawRect(Center, 0, 0, 20000, $"P6B_解限波动炮_跑动方向绘图_直线_{isSafe}", + startLineRad, 2f, 15f, isSafe, draw: false); + var dp2 = sa.DrawFan(Center, 0, 0, 20000, $"P6B_解限波动炮_跑动方向绘图_路径_{isSafe}", + 135f.DegToRad(), startDonutRad, 15f, 13f, isSafe, draw: false); + dp1.Color = dp1.Color.WithW(3f); + dp2.Color = dp2.Color.WithW(3f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp2); + + sa.Method.RemoveDraw($"P6B_解限波动炮_跑动方向绘图_路径_{!isSafe}"); + sa.Method.RemoveDraw($"P6B_解限波动炮_跑动方向绘图_直线_{!isSafe}"); + } + } + + [ScriptMethod(name: "P6B_解限波动炮_脚底黄圈", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31663"], + userControl: Debugging, suppress: 500)] + public void P6B_解限波动炮_脚底黄圈(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.玩家脚下黄圈轮数++; + switch (_p6.玩家脚下黄圈轮数) + { + case 1: + _p6.脚下出现黄圈.Set(); + sa.Method.RemoveDraw($"P6A_解限波动炮_指路场中"); + break; + case 4: + _p6.脚下出现黄圈.Reset(); + _p6.解限波动炮跑动方向绘图.Reset(); + break; + } + + switch (_parse) + { + case 6.1 when _p6.玩家脚下黄圈轮数 == 5: + _p6.波动炮八方绘图开启.Set(); + break; + case 6.2 when _p6.玩家脚下黄圈轮数 == 6: + _p6.宇宙龙炎绘图开启.Set(); + break; + } + } + + #endregion 解限波动炮 + + #region P6C 宇宙流星 + + [ScriptMethod(name: "———————— 《P6C 宇宙流星》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P6C_宇宙流星_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P6C_宇宙流星_分P", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31664"], + userControl: Debugging)] + public void P6C_宇宙流星_分P(Event ev, ScriptAccessory sa) + { + _parse = 6.3; + _p6.ResetAutoAttack(sa, true); + sa.DebugMsg($"当前阶段为:{_parse}", Debugging); + } + + [ScriptMethod(name: "P6C_宇宙流星_指路场中与后续八方", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31664"], + userControl: true)] + public void P6C_宇宙流星_指路场中与后续八方(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.DrawGuidance(Center, 0, 10000, $"P6C_宇宙流星_指路场中与后续八方"); + List partySpreadRegion = [5, 2, 6, 0, 7, 1, 4, 3]; + var pos = new Vector3(100, 0, 114f).RotateAndExtend(Center, partySpreadRegion[sa.GetMyIndex()] * 45f.DegToRad()); + sa.DrawGuidance(Center, pos, 0, 20000, $"P6C_宇宙流星_指路场中与后续八方", isSafe: false); + } + + [ScriptMethod(name: "P6C_宇宙流星_删除场中指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31666"], + userControl: Debugging, suppress: 500)] + public void P6C_宇宙流星_删除场中指路(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.Method.RemoveDraw($"P6C_宇宙流星_指路场中与后续八方"); + } + + [ScriptMethod(name: "P6C_宇宙流星_八方指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31666"], + userControl: true, suppress: 500)] + public void P6C_宇宙流星_八方指路(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + List partySpreadRegion = [5, 2, 6, 0, 7, 1, 4, 3]; + var pos = new Vector3(100, 0, 114f).RotateAndExtend(Center, partySpreadRegion[sa.GetMyIndex()] * 45f.DegToRad()); + sa.DrawGuidance(pos, 0, 20000, $"P6C_宇宙流星_八方指路", isSafe: true); + for (int i = 0; i < 8; i++) + { + var dp = sa.DrawLine(Center, 0, 0, 20000, $"P6C_宇宙流星_指引线{i}", partySpreadRegion[i] * 45f.DegToRad(), 40f, 20f, draw: false); + dp.Color = i switch + { + 0 or 1 => new Vector4(0.1f, 0.1f, 1, 1), + 2 or 3 => new Vector4(0.1f, 1f, 0.1f, 1), + _ => new Vector4(1, 0.1f, 0.1f, 1), + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + } + + [ScriptMethod(name: "P6C_宇宙流星_删除八方指路与指引线", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:32699"], + userControl: Debugging, suppress: 500)] + public void P6C_宇宙流星_删除八方指路与指引线(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + sa.Method.RemoveDraw($"P6C_宇宙流星_八方指路"); + sa.Method.RemoveDraw($"P6C_宇宙流星_指引线.*"); + } + + [ScriptMethod(name: "P6C_宇宙流星_陨石目标收集", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:015A"], + userControl: Debugging)] + public void P6C_宇宙流星_陨石目标收集(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + lock (_p6.陨石目标) + { + _p6.陨石目标.Add(sa.GetPlayerIdIndex((uint)ev.TargetId)); + if (_p6.陨石目标.Count < 3) return; + _p6.陨石目标收集完毕.Set(); + } + } + + [ScriptMethod(name: "P6C_宇宙流星_陨石目标连线", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:015A"], + userControl: Debugging, suppress: 500)] + public void P6C_宇宙流星_陨石目标连线(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.陨石目标收集完毕.WaitOne(); + + var dp1 = sa.DrawGuidance(sa.Data.PartyList[_p6.陨石目标[0]], sa.Data.PartyList[_p6.陨石目标[1]], + 0, 10000, $"P6C_宇宙流星_陨石目标连线与指路01", draw: false); + var dp2 = sa.DrawGuidance(sa.Data.PartyList[_p6.陨石目标[1]], sa.Data.PartyList[_p6.陨石目标[2]], + 0, 10000, $"P6C_宇宙流星_陨石目标连线与指路12", draw: false); + var dp3 = sa.DrawGuidance(sa.Data.PartyList[_p6.陨石目标[2]], sa.Data.PartyList[_p6.陨石目标[0]], + 0, 10000, $"P6C_宇宙流星_陨石目标连线与指路20", draw: false); + dp1.Color = new Vector4(0f, 0f, 0f, 3f); + dp2.Color = new Vector4(0f, 0f, 0f, 3f); + dp3.Color = new Vector4(0f, 0f, 0f, 3f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp1); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp2); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp3); + dp1.Color = new Vector4(1f, 1f, 0f, 1f); + dp2.Color = new Vector4(1f, 1f, 0f, 1f); + dp3.Color = new Vector4(1f, 1f, 0f, 1f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp1); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp2); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp3); + } + + [ScriptMethod(name: "P6C_宇宙流星_陨石指路", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:015A"], + userControl: Debugging, suppress: 500)] + public void P6C_宇宙流星_陨石指路(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.陨石目标收集完毕.WaitOne(); + + // 人群安全区 + var crowdSafeRegion = _p6.陨石目标.Contains(6) ? 0 : 2; + + if (_p6.陨石目标.Contains(sa.GetMyIndex())) + { + // 陨石目标包含自身,则画危险区与指引线 + + sa.DrawFan(Center, 0, 20000, $"P6C_宇宙流星_人群安全区示意", + 150f.DegToRad(), crowdSafeRegion * 90f.DegToRad(), 30f, 0f); + + for (int i = 0; i < 3; i++) + { + sa.DrawLine(Center, 0, 0, 20000, $"P6C_宇宙流星_陨石指引线{i}", + (i - 1) * 90f.DegToRad(), 40f, 20f, isSafe: true); + } + } + else + { + sa.DrawGuidance(new Vector3(100, 0, 114.5f).RotateAndExtend(Center, crowdSafeRegion * 90f.DegToRad()), 0, + 20000, $"P6C_宇宙流星_陨石指路"); + } + } + + [ScriptMethod(name: "P6C_宇宙流星_删除宇宙流星绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31668"], + userControl: Debugging, suppress: 500)] + public void P6C_宇宙流星_删除宇宙流星绘图(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + _p6.陨石目标收集完毕.Reset(); + _p6.Reset(sa); + _p6.Dispose(); + sa.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "P6C_宇宙流星_机制结束后", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31668"], + userControl: true, suppress: 500)] + public void P6C_宇宙流星_机制结束后(Event ev, ScriptAccessory sa) + { + if (_parse < 6) return; + // 如果全员存活 + foreach (var member in sa.Data.PartyList) + { + var obj = sa.GetById(member); + if (obj == null) return; + if (obj.IsDead) return; + } + sa.Method.TextInfo($"超越极限,迎接胜利吧!", 2000); + } + + #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 参数容器类 + + private class P1StateParams + { + public ulong BossId = 0; + + public Dictionary 塔字典 = new(); + public Dictionary 接线绘图字典 = new(); + public int 线塔轮次 = 0; + public DateTime 塔触发时间 = DateTime.MinValue; + public ManualResetEvent 每轮塔收集完毕 = new ManualResetEvent(false); + public ManualResetEvent 前一轮绘图清除完毕 = new ManualResetEvent(false); + public string 扫描接线Framework = ""; + public string 闲人指路Framework = ""; + public int 上一次靠近状态 = 0; + public bool 扫描接线开启 = false; + + public int 全能之主轮次 = 0; + public bool 全能之主为顺时针 = false; + public bool 全能之主起跑线绘制完毕 = false; + public bool 全能之主顺逆时针判断完毕 = false; + public float 全能之主第一次角度寄存 = -10; + public ManualResetEvent 全能之主顺逆时针判断完毕事件 = new ManualResetEvent(false); + public ManualResetEvent 全能之主第一次角度寄存完毕事件 = new ManualResetEvent(false); + public int 上一次远距离状态 = 0; + public string 全能之主出去时间Framework = ""; + public string 全能之主最远距离Framework = ""; + public int 扩散波动炮次数 = 0; + + public void Reset(ScriptAccessory sa) + { + BossId = 0; + 塔字典 = new Dictionary(); + 接线绘图字典 = new Dictionary(); + 线塔轮次 = 0; + 塔触发时间 = DateTime.MinValue; + + 上一次靠近状态 = 0; + 扫描接线开启 = false; + 全能之主轮次 = 0; + 全能之主为顺时针 = false; + 全能之主起跑线绘制完毕 = false; + 全能之主顺逆时针判断完毕 = false; + 全能之主第一次角度寄存 = -10; + + 上一次远距离状态 = 0; + sa.Method.UnregistFrameworkUpdateAction(闲人指路Framework); + sa.Method.UnregistFrameworkUpdateAction(扫描接线Framework); + sa.Method.UnregistFrameworkUpdateAction(全能之主出去时间Framework); + sa.Method.UnregistFrameworkUpdateAction(全能之主最远距离Framework); + + 闲人指路Framework = ""; + 扫描接线Framework = ""; + 全能之主出去时间Framework = ""; + 全能之主最远距离Framework = ""; + + sa.DebugMsg($"P1参数被reset", Debugging); + } + + public void Dispose() + { + 每轮塔收集完毕.Dispose(); + 前一轮绘图清除完毕.Dispose(); + 全能之主顺逆时针判断完毕事件.Dispose(); + 全能之主第一次角度寄存完毕事件.Dispose(); + } + + public void Register() + { + 每轮塔收集完毕 = new ManualResetEvent(false); + 前一轮绘图清除完毕 = new ManualResetEvent(false); + 全能之主顺逆时针判断完毕事件 = new ManualResetEvent(false); + 全能之主第一次角度寄存完毕事件 = new ManualResetEvent(false); + 每轮塔收集完毕.Reset(); + 前一轮绘图清除完毕.Reset(); + 全能之主顺逆时针判断完毕事件.Reset(); + 全能之主第一次角度寄存完毕事件.Reset(); + } + } + + private class P2StateParams + { + public ulong BossIdMale = 0; + public ulong BossIdFemale = 0; + + public int 上一次防火墙状态 = 0; + public bool 协作程序是远线 = false; + public int 可选目标数量 = 0; + public bool 使能防火墙 = false; + public bool 转场手臂先正三角 = false; + public int 大眼睛方位 = 0; + public int 男人钢铁方位 = 0; + + public ManualResetEvent 眼睛激光方位记录 = new(false); + public ManualResetEvent 分摊记录 = new(false); + public ManualResetEvent 男人钢铁方位记录 = new(false); + public ManualResetEvent 女人击退记录 = new(false); + public ManualResetEvent 男人分摊钢铁记录 = new(false); + public ManualResetEvent 眼睛激光准备绘图 = new(false); + public ManualResetEvent 转场头标记录 = new(false); + + public string 判断防火墙目标Framework = ""; + + public void Reset(ScriptAccessory sa) + { + BossIdMale = 0; + BossIdFemale = 0; + + 上一次防火墙状态 = 0; + 协作程序是远线 = false; + 可选目标数量 = 0; + 使能防火墙 = false; + 转场手臂先正三角 = false; + 大眼睛方位 = 0; + 男人钢铁方位 = 0; + + sa.Method.UnregistFrameworkUpdateAction(判断防火墙目标Framework); + 判断防火墙目标Framework = ""; + } + + public void Dispose() + { + 眼睛激光方位记录.Dispose(); + 分摊记录.Dispose(); + 男人钢铁方位记录.Dispose(); + 女人击退记录.Dispose(); + 男人分摊钢铁记录.Dispose(); + 眼睛激光准备绘图.Dispose(); + 转场头标记录.Dispose(); + } + + public void Register() + { + 眼睛激光方位记录 = new ManualResetEvent(false); + 分摊记录 = new ManualResetEvent(false); + 男人钢铁方位记录 = new ManualResetEvent(false); + 女人击退记录 = new ManualResetEvent(false); + 男人分摊钢铁记录 = new ManualResetEvent(false); + 眼睛激光准备绘图 = new ManualResetEvent(false); + 转场头标记录 = new ManualResetEvent(false); + 眼睛激光方位记录.Reset(); + 分摊记录.Reset(); + 男人钢铁方位记录.Reset(); + 女人击退记录.Reset(); + 男人分摊钢铁记录.Reset(); + 眼睛激光准备绘图.Reset(); + 转场头标记录.Reset(); + } + } + + private class P3StateParams + { + public ulong BossId = 0; + public int 你好世界轮数 = 0; + + public int 红塔位置 = 0; + public int 蓝塔位置 = 0; + public bool 大圈是红塔 = false; + public bool 光头小电视方向打右 = false; + public int 小电视玩家面向 = 0; + + public ManualResetEvent 红蓝塔方位记录 = new(false); + public ManualResetEvent 你好世界轮数记录 = new(false); + public ManualResetEvent 小电视头标记录 = new(false); + public ManualResetEvent 光头扫描方向记录 = new(false); + public ManualResetEvent 小电视玩家面向记录 = new(false); + + public string 小电视面向辅助Framework = ""; + public DateTime 小电视面向辅助触发时间 = DateTime.MinValue; + + public void Reset(ScriptAccessory sa) + { + BossId = 0; + 你好世界轮数 = 0; + 红塔位置 = 0; + 蓝塔位置 = 0; + 大圈是红塔 = false; + 光头小电视方向打右 = false; + + 小电视玩家面向 = 0; + 小电视面向辅助触发时间 = DateTime.MinValue; + sa.Method.UnregistFrameworkUpdateAction(小电视面向辅助Framework); + 小电视面向辅助Framework = ""; + } + + public void Dispose() + { + 红蓝塔方位记录.Dispose(); + 你好世界轮数记录.Dispose(); + 小电视头标记录.Dispose(); + 光头扫描方向记录.Dispose(); + 小电视玩家面向记录.Dispose(); + } + + public void Register() + { + 红蓝塔方位记录 = new ManualResetEvent(false); + 你好世界轮数记录 = new ManualResetEvent(false); + 小电视头标记录 = new ManualResetEvent(false); + 光头扫描方向记录 = new ManualResetEvent(false); + 小电视玩家面向记录 = new ManualResetEvent(false); + 红蓝塔方位记录.Reset(); + 你好世界轮数记录.Reset(); + 小电视头标记录.Reset(); + 光头扫描方向记录.Reset(); + 小电视玩家面向记录.Reset(); + } + } + + private class P4StateParams + { + public ulong BossId = 0; + public int 蓝屏波动炮轮数 = 0; + public ManualResetEvent 波动炮初始化记录 = new(false); + public void Reset(ScriptAccessory sa) + { + BossId = 0; + 蓝屏波动炮轮数 = 0; + } + + public void Dispose() + { + 波动炮初始化记录.Dispose(); + } + + public void Register() + { + 波动炮初始化记录 = new ManualResetEvent(false); + } + } + + private class P5AStateParams + { + public int 蟑螂位置 = 0; + public int 光头位置 = 0; + public int 玩家四分之一半场 = -1; + public int 拳头数量 = 0; + public int 拳头颜色 = 0; + public int 玩家引导激光手方位 = 0; + public int 光头左右扫描 = 0; + public int 玩家左右扫描 = 0; + public int 蟑螂左右刀 = 0; + + public Dictionary 激光手方向字典 = new(); + + public bool 开启玩家引导激光手指路 = false; + public bool 第一根远线拉断 = false; + + public ManualResetEvent 远线搭档记录 = new(false); + public ManualResetEvent 头标记录 = new(false); + public ManualResetEvent 光头蟑螂定位 = new(false); + public ManualResetEvent 拳头记录 = new(false); + public ManualResetEvent 盾连击记录 = new(false); + public ManualResetEvent 蟑螂左右刀记录 = new(false); + + public string 小电视面向辅助Framework = ""; + public DateTime 小电视面向辅助触发时间 = DateTime.MinValue; + + // 一键重置方法 + public void Reset(ScriptAccessory sa) + { + 蟑螂位置 = 0; + 光头位置 = 0; + 玩家四分之一半场 = -1; + 拳头数量 = 0; + 拳头颜色 = 0; + 玩家引导激光手方位 = 0; + 光头左右扫描 = 0; + 玩家左右扫描 = 0; + 蟑螂左右刀 = 0; + + 开启玩家引导激光手指路 = false; + 第一根远线拉断 = false; + + 激光手方向字典 = new Dictionary(); + 小电视面向辅助触发时间 = DateTime.MinValue; + + sa.Method.UnregistFrameworkUpdateAction(小电视面向辅助Framework); + 小电视面向辅助Framework = ""; + } + + public void Dispose() + { + 远线搭档记录.Dispose(); + 头标记录.Dispose(); + 光头蟑螂定位.Dispose(); + 拳头记录.Dispose(); + 盾连击记录.Dispose(); + 蟑螂左右刀记录.Dispose(); + } + + public void Register() + { + 远线搭档记录 = new ManualResetEvent(false); + 头标记录 = new ManualResetEvent(false); + 光头蟑螂定位 = new ManualResetEvent(false); + 拳头记录 = new ManualResetEvent(false); + 盾连击记录 = new ManualResetEvent(false); + 蟑螂左右刀记录 = new ManualResetEvent(false); + 远线搭档记录.Reset(); + 头标记录.Reset(); + 光头蟑螂定位.Reset(); + 拳头记录.Reset(); + 盾连击记录.Reset(); + 蟑螂左右刀记录.Reset(); + } + } + + private class P5BStateParams + { + public bool 协作程序是远线 = false; + public int 男人方位 = 0; + public int 玩家八方方位 = 0; + public int 玩家踩塔方位 = 0; + public Dictionary 塔方位类型字典 = new(); // 方位、是否是双人塔 + public ManualResetEvent 八方头标记录完毕 = new(false); + public ManualResetEvent 塔方位记录完毕 = new(false); + public ManualResetEvent 踩塔处理完毕 = new(false); + + public int 女人方位 = 0; + public bool 女人是十字外安全 = false; + public bool 圆环是顺时针 = false; + public ulong 圆环Id = 0; + public ulong 二传女人Id = 0; + public int 圆环攻击次数 = 0; + public ManualResetEvent 转圈头标记录完毕 = new(false); + public ManualResetEvent 圆环方向记录完毕 = new(false); + public ManualResetEvent 女人技能记录完毕 = new(false); + public ManualResetEvent 女人十字辣翅释放完毕 = new(false); + + public void Reset(ScriptAccessory sa) + { + 协作程序是远线 = false; + 男人方位 = 0; + 玩家八方方位 = 0; + 玩家踩塔方位 = 0; + 塔方位类型字典 = new Dictionary(); // 方位、是否是双人塔 + + 女人方位 = 0; + 女人是十字外安全 = false; + 圆环是顺时针 = false; + 圆环Id = 0; + 二传女人Id = 0; + 圆环攻击次数 = 0; + } + + public void Dispose() + { + 八方头标记录完毕.Dispose(); + 塔方位记录完毕.Dispose(); + 踩塔处理完毕.Dispose(); + + 转圈头标记录完毕.Dispose(); + 女人十字辣翅释放完毕.Dispose(); + 圆环方向记录完毕.Dispose(); + 女人技能记录完毕.Dispose(); + } + + public void Register() + { + 八方头标记录完毕 = new ManualResetEvent(false); + 塔方位记录完毕 = new ManualResetEvent(false); + 踩塔处理完毕 = new ManualResetEvent(false); + + 转圈头标记录完毕 = new ManualResetEvent(false); + 女人十字辣翅释放完毕 = new ManualResetEvent(false); + 圆环方向记录完毕 = new ManualResetEvent(false); + 女人技能记录完毕 = new ManualResetEvent(false); + 八方头标记录完毕.Reset(); + 塔方位记录完毕.Reset(); + 踩塔处理完毕.Reset(); + 转圈头标记录完毕.Reset(); + 女人十字辣翅释放完毕.Reset(); + 圆环方向记录完毕.Reset(); + 女人技能记录完毕.Reset(); + } + + public int FindPlayerTower(ScriptAccessory sa) + { + int[] towerRegionOffsets = [2, 1, -2, -1]; + + int bestTower = -1; + int highestScore = -1; + + foreach (int offset in towerRegionOffsets) + { + int region = (玩家八方方位 + offset + 16) % 16; + if (!塔方位类型字典.TryGetValue(region, out bool isDoubleTower)) + continue; + sa.DebugMsg($"检测到 方位{region} 存在 {(isDoubleTower ? "双" : "单")}人塔", Debugging); + int score = 1 + (isDoubleTower ? 10 : 0); + if (score <= highestScore) continue; + highestScore = score; + bestTower = region; + if (isDoubleTower) break; + } + + sa.DebugMsg($"玩家所需踩塔:{bestTower}", Debugging); + return bestTower; + } + } + + private class P5CStateParams + { + public Dictionary 组合技记录 = new(); // 方位、(技能类型、是不是第一轮) + public ManualResetEvent 男女人组合技记录完毕 = new(false); + public ManualResetEvent 组合技安全区记录完毕 = new(false); + public ManualResetEvent 第一段组合技结束 = new(false); + public int[] 组合技安全区 = []; + + public int 蟑螂方位 = 0; + public ulong 蟑螂Id = 0; + public bool 扫描接线开启 = false; + public ManualResetEvent 三传头标记录完毕 = new(false); + public ManualResetEvent 四传头标记录完毕 = new(false); + public ManualResetEvent 蟑螂方位记录完毕 = new(false); + public Dictionary 接线绘图字典 = new(); + + public string 扫描接线Framework = ""; + + public Dictionary> 技能类型安全区字典 = new(); // 方位、技能类型 + public void Reset(ScriptAccessory sa) + { + 蟑螂方位 = 0; + 蟑螂Id = 0; + 扫描接线开启 = false; + 组合技记录 = new Dictionary(); + 组合技安全区 = []; + 接线绘图字典 = new Dictionary(); + + sa.Method.UnregistFrameworkUpdateAction(扫描接线Framework); + 扫描接线Framework = ""; + + // 0~3钢铁,4~7月环,8~11十字,12~15辣尾,16光头打左右,17光头打上下 + 技能类型安全区字典 = new Dictionary> + { + { 4, [true, true, false, false, true, true, false, false, false, false, false, false] }, + { 5, [false, true, true, false, false, true, true, false, false, false, false, false] }, + { 6, [false, false, true, true, false, false, true, true, false, false, false, false] }, + { 7, [true, false, false, true, true, false, false, true, false, false, false, false] }, + { 0, [false, false, true, true, false, false, true, true, true, true, true, true] }, + { 1, [true, false, false, true, true, false, false, true, true, true, true, true] }, + { 2, [true, true, false, false, true, true, false, false, true, true, true, true] }, + { 3, [false, true, true, false, false, true, true, false, true, true, true, true] }, + { 8, [false, false, false, false, false, false, true, true, false, false, true, true] }, + { 9, [false, false, false, false, true, false, false, true, true, false, false, true] }, + { 10, [false, false, false, false, true, true, false, false, true, true, false, false] }, + { 11, [false, false, false, false, false, true, true, false, false, true, true, false] }, + { 12, [true, true, true, true, false, false, false, false, false, false, false, false] }, + { 13, [true, true, true, true, false, false, false, false, false, false, false, false] }, + { 14, [true, true, true, true, false, false, false, false, false, false, false, false] }, + { 15, [true, true, true, true, false, false, false, false, false, false, false, false] }, + { 16, [true, false, true, false, true, false, true, false, true, false, true, false] }, + { 17, [false, true, false, true, false, true, false, true, false, true, false, true] } + }; + } + + public void Dispose() + { + 男女人组合技记录完毕.Dispose(); + 组合技安全区记录完毕.Dispose(); + 第一段组合技结束.Dispose(); + 三传头标记录完毕.Dispose(); + 四传头标记录完毕.Dispose(); + 蟑螂方位记录完毕.Dispose(); + } + + public void Register() + { + 男女人组合技记录完毕 = new ManualResetEvent(false); + 组合技安全区记录完毕 = new ManualResetEvent(false); + 第一段组合技结束 = new ManualResetEvent(false); + 三传头标记录完毕 = new ManualResetEvent(false); + 四传头标记录完毕 = new ManualResetEvent(false); + 蟑螂方位记录完毕 = new ManualResetEvent(false); + 男女人组合技记录完毕.Reset(); + 组合技安全区记录完毕.Reset(); + 第一段组合技结束.Reset(); + 三传头标记录完毕.Reset(); + 四传头标记录完毕.Reset(); + 蟑螂方位记录完毕.Reset(); + } + + public void FindComboAttackSafePoint(ScriptAccessory sa) + { + // 使用局部函数统一处理两轮逻辑,彻底消除重复代码 + // return (FindSafePointForRound(isFirstRound: true, sa), FindSafePointForRound(isFirstRound: false, sa)); + 组合技安全区 = [FindSafePointForRound(isFirstRound: true, sa), FindSafePointForRound(isFirstRound: false, sa)]; + } + + private int FindSafePointForRound(bool isFirstRound, ScriptAccessory sa) + { + var roundSkills = 组合技记录.Where(kvp => kvp.Value.Item2 == isFirstRound).ToList(); + bool[] safePoints = new bool[12]; Array.Fill(safePoints, true); + foreach (var (region, (skill, _)) in roundSkills) + { + sa.DebugMsg($"在方位{region}发现第{(isFirstRound ? "一" : "二")}轮技能{skill}", Debugging); + var skillSafeZones = 技能类型安全区字典[skill]; + for (int i = 0; i < 12; i++) + safePoints[i] &= skillSafeZones[i]; + } + int safeIndex = Array.IndexOf(safePoints, true); + sa.DebugMsg(safeIndex == -1 + ? $"第{(isFirstRound ? "一" : "二")}轮未找到安全点" + : $"第{(isFirstRound ? "一" : "二")}轮安全点索引: {safeIndex}", Debugging); + + return safeIndex; + } + } + + private class P6StateParams + { + public ulong BossId = 0; + public bool 使能普通攻击绘图 = false; + public string 普通攻击绘图Framework = ""; + public bool 已绘制远离指示线 = false; + public bool 已提示建立一仇 = false; + public int 最远玩家Idx = -1; + public int 一仇玩家Idx = -1; + + public bool 宇宙天箭是内天箭 = false; + public bool 宇宙天箭类型判断完毕 = false; + public int 宇宙天箭判定次数 = 0; + public int 宇宙天箭绘图图案序号 = -1; + public int 宇宙天箭指路图案序号 = -1; + public string 宇宙天箭绘图Framework = ""; + public string 宇宙天箭指路Framework = ""; + + public int 解限波动炮第一炮方位 = -1; + public bool 解限波动炮是顺时针 = false; + public int 玩家脚下黄圈轮数 = 0; + public bool 解限波动炮方向判断完毕 = false; + + public int 波动炮判定次数 = 0; + + public List 陨石目标 = []; + + public ManualResetEvent 宇宙天箭读条开始 = new(false); + public ManualResetEvent 宇宙龙炎绘图开启 = new(false); + public ManualResetEvent 波动炮八方绘图开启 = new(false); + public ManualResetEvent 解限波动炮跑动方向绘图 = new(false); + public ManualResetEvent 脚下出现黄圈 = new(false); + public ManualResetEvent 波动炮分摊绘图开启 = new(false); + public ManualResetEvent 陨石目标收集完毕 = new(false); + + public void Reset(ScriptAccessory sa) + { + BossId = 0; + 陨石目标 = []; + ResetAutoAttack(sa, true); + ResetCosmoArrow(sa); + ResetUnlimitedWaveCannon(); + ResetSpreadWaveCannon(); + } + + public void ResetAutoAttack(ScriptAccessory sa, bool unRegist) + { + 使能普通攻击绘图 = false; + 已绘制远离指示线 = false; + 已提示建立一仇 = false; + 最远玩家Idx = -1; + 一仇玩家Idx = -1; + sa.Method.RemoveDraw($"P6_普通攻击绘图.*"); + if (!unRegist) return; + sa.Method.UnregistFrameworkUpdateAction(普通攻击绘图Framework); + } + + public void ResetCosmoArrow(ScriptAccessory sa) + { + 宇宙天箭是内天箭 = false; + 宇宙天箭类型判断完毕 = false; + 宇宙天箭判定次数 = 0; + 宇宙天箭绘图图案序号 = -1; + 宇宙天箭指路图案序号 = -1; + sa.Method.UnregistFrameworkUpdateAction(宇宙天箭绘图Framework); + sa.Method.UnregistFrameworkUpdateAction(宇宙天箭指路Framework); + } + + public void ResetUnlimitedWaveCannon() + { + 解限波动炮第一炮方位 = -1; + 解限波动炮是顺时针 = false; + 玩家脚下黄圈轮数 = 0; + 解限波动炮方向判断完毕 = false; + } + + public void ResetSpreadWaveCannon() + { + 波动炮判定次数 = 0; + } + + public void Dispose() + { + 宇宙天箭读条开始.Dispose(); + 宇宙龙炎绘图开启.Dispose(); + 波动炮八方绘图开启.Dispose(); + 解限波动炮跑动方向绘图.Dispose(); + 脚下出现黄圈.Dispose(); + 波动炮分摊绘图开启.Dispose(); + 陨石目标收集完毕.Dispose(); + } + + public void Register() + { + 宇宙天箭读条开始 = new ManualResetEvent(false); + 宇宙龙炎绘图开启 = new ManualResetEvent(false); + 波动炮八方绘图开启 = new ManualResetEvent(false); + 解限波动炮跑动方向绘图 = new ManualResetEvent(false); + 脚下出现黄圈 = new ManualResetEvent(false); + 波动炮分摊绘图开启 = new ManualResetEvent(false); + 陨石目标收集完毕 = new ManualResetEvent(false); + 宇宙天箭读条开始.Reset(); + 宇宙龙炎绘图开启.Reset(); + 波动炮八方绘图开启.Reset(); + 解限波动炮跑动方向绘图.Reset(); + 脚下出现黄圈.Reset(); + 波动炮分摊绘图开启.Reset(); + 陨石目标收集完毕.Reset(); + } + } + #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 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 IEnumerable GetByDataId(this ScriptAccessory sa, uint dataId) + { + return sa.Data.Objects.Where(x => x.DataId == dataId); + } + + 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 List GetTetherSource(this ScriptAccessory sa, IBattleChara? battleChara, uint tetherId) + { + List tetherSourceId = []; + if (battleChara == null || !battleChara.IsValid()) return []; + unsafe + { + BattleChara* chara = (BattleChara*)battleChara.Address; + var tetherList = chara->Vfx.Tethers; + + foreach (var tether in tetherList) + { + if (tether.Id != tetherId) continue; + tetherSourceId.Add(tether.TargetId.ObjectId); + } + } + return tetherSourceId; + } + + public static unsafe byte? GetTransformationId(this ScriptAccessory sa, IGameObject? obj) + { + if (obj == null) return null; + Character* objStruct = (Character*)obj.Address; + return objStruct->Timeline.ModelState; + } + +} +#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 = 0) + { + 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 0; + var digitChar = valStr[length - x]; // 从右往左取第x位 + return int.Parse(digitChar.ToString()); + } + + /// + /// 获得两个弧度(rad到radReference)的差值,逆时针增加大于0 + /// + /// 取值角度 + /// 参考角度 + /// + public static float GetDiffRad(this float rad, float radReference) + { + var diff = (rad - radReference + 4 * float.Pi) % (2 * float.Pi); + if (diff > float.Pi) diff -= 2 * float.Pi; + return diff; + } +} + +#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); + } + + /// + /// 输入位置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]; + } + +} +#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, innerScale == 0 ? DrawTypeEnum.Fan : DrawTypeEnum.Donut, 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 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); + + /// + /// 赋予输入的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; + } +} + +#endregion 绘图函数 + +#region 调试函数 + +public static class DebugFunction +{ + public static void DebugMsg(this ScriptAccessory sa, string msg, bool enable = true, bool showInChatBox = false) + { + if (!enable) return; + sa.Log.Debug(msg); + if (!showInChatBox) return; + sa.Method.SendChat($"/e {msg}"); + } +} + +#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 ScaleModify(this ScriptAccessory sa, IGameObject? obj, float scale, bool vfxScaled = true) + { + sa.Method.RunOnMainThreadAsync(Action); + void Action() + { + if (obj == null) return; + GameObject* charaStruct = (GameObject*)obj.Address; + if (!obj.IsValid() || !charaStruct->IsReadyToDraw()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + charaStruct->Scale = scale; + if (vfxScaled) + charaStruct->VfxScale = scale; + + if (charaStruct->IsCharacter()) + ((BattleChara*)charaStruct)->Character.CharacterData.ModelScale = 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 unsafe bool IsMoving(this ScriptAccessory sa) + { + FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentMap* ptr = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentMap.Instance(); + return ptr is not null && ptr->IsPlayerMoving; + } + + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + const VisibilityFlags VISIBLE_FLAG = VisibilityFlags.None; + const VisibilityFlags INVISIBILITY_FLAG = VisibilityFlags.Model; + try + { + var flagsPtr = &((GameObject*)actor?.Address)->RenderFlags; + *flagsPtr = visible ? VISIBLE_FLAG : INVISIBILITY_FLAG; + } + catch (Exception e) + { + sa.Log.Error(e.ToString()); + throw; + } + } + +} + +#endregion 特殊函数 + +#endregion 函数集 + diff --git a/lianying1997/06-EndWalker/Zodiark.cs b/lianying1997/06-EndWalker/Zodiark.cs new file mode 100644 index 0000000..341004e --- /dev/null +++ b/lianying1997/06-EndWalker/Zodiark.cs @@ -0,0 +1,636 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Network.Structures.InfoProxy; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs; +using KodakkuAssist.Module.Script.Type; + +namespace UsamisScript.EndWalker.Zodiark; + +[ScriptType(name: "Zodiark [佐迪亚克歼灭战]", territorys: [992], + guid: "ed3f32fe-fc12-4485-baa4-bec6c357e70b", version: "0.0.0.3", author: "Usami", note: noteStr, updateInfo: UpdateInfo)] +public class Zodiark +{ + const string noteStr = + """ + v0.0.0.3: + 鸭门。 + """; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public bool DebugMode { get; set; } = false; + + // TODO + // 加一个阶段指示,转场。转场的秘纹绘图时间调整一下 + // 以及,星落部分,不要让画面过乱。根据地图特效直接绘出星落安全区。 + + int ParadeigmaNum = 0; // 范式次数 + Vector3[] BirdOrBeastPos = new Vector3[4]; // Quetzalcoatl & Behemoth,记录四个角落月环鸟鸟/钢铁贝贝的位置 + Vector3[] SnakePos = new Vector3[16]; // Python,记录十六个区域蛇蛇直线的位置 + Vector3[] SnakePosTarget = new Vector3[16]; // Python,记录十六个区域蛇蛇最后目标的位置 + List EsoterikosSourceIds = []; // 已记录的秘纹ID + public ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1f, 0f, 0f, 1.0f) }; + bool isTurnLeft = false; // 爸爸转哪儿 + bool isStarFall = false; // 转场阶段,为三重秘纹绘图 + Dictionary? AstralMapping = null; + int AstralNum = 0; // 星落次数 + int AstralType = 0; // 星落安全区类型 + Vector3[] AstralSafePos = new Vector3[6]; // 星落6个安全点 + public void Init(ScriptAccessory accessory) + { + ParadeigmaNum = 0; // 范式次数 + + // 记录内场角位置 + BirdOrBeastPos[0] = new(89.50f, 0, 89.50f); // 左上 + BirdOrBeastPos[1] = new(110.50f, 0, 89.50f); // 右上 + BirdOrBeastPos[2] = new(89.50f, 0, 110.50f); // 左下 + BirdOrBeastPos[3] = new(110.50f, 0, 110.50f); // 右下 + + // 蛇的16个位置,从左上开始顺时针 + SnakePos[0] = new(85.00f, 0, 75.00f); + SnakePos[1] = new(95.00f, 0, 75.00f); + SnakePos[2] = new(105.00f, 0, 75.00f); + SnakePos[3] = new(115.00f, 0, 75.00f); + + SnakePos[4] = new(125.00f, 0, 85.00f); + SnakePos[5] = new(125.00f, 0, 95.00f); + SnakePos[6] = new(125.00f, 0, 105.00f); + SnakePos[7] = new(125.00f, 0, 115.00f); + + SnakePos[8] = new(115.00f, 0, 125.00f); + SnakePos[9] = new(105.00f, 0, 125.00f); + SnakePos[10] = new(95.00f, 0, 125.00f); + SnakePos[11] = new(85.00f, 0, 125.00f); + + SnakePos[12] = new(75.00f, 0, 115.00f); + SnakePos[13] = new(75.00f, 0, 105.00f); + SnakePos[14] = new(75.00f, 0, 95.00f); + SnakePos[15] = new(75.00f, 0, 85.00f); + + // 蛇的16个目标位置 + SnakePosTarget[0] = SnakePos[11]; + SnakePosTarget[1] = SnakePos[10]; + SnakePosTarget[2] = SnakePos[9]; + SnakePosTarget[3] = SnakePos[8]; + SnakePosTarget[4] = SnakePos[15]; + SnakePosTarget[5] = SnakePos[14]; + SnakePosTarget[6] = SnakePos[13]; + SnakePosTarget[7] = SnakePos[12]; + SnakePosTarget[8] = SnakePos[3]; + SnakePosTarget[9] = SnakePos[2]; + SnakePosTarget[10] = SnakePos[1]; + SnakePosTarget[11] = SnakePos[0]; + SnakePosTarget[12] = SnakePos[7]; + SnakePosTarget[13] = SnakePos[6]; + SnakePosTarget[14] = SnakePos[5]; + SnakePosTarget[15] = SnakePos[4]; + + // 星落6个安全点 + AstralSafePos[0] = new(86, 0, 86); + AstralSafePos[1] = new(100, 0, 86); + AstralSafePos[2] = new(114, 0, 86); + AstralSafePos[3] = new(86, 0, 100); + AstralSafePos[4] = new(100, 0, 100); + AstralSafePos[5] = new(114, 0, 100); + + EsoterikosSourceIds = []; // 已记录的秘纹ID + + isTurnLeft = false; // 爸爸转哪儿 + isStarFall = false; // 转场阶段,为三重秘纹绘图 + + // 星落字典 + AstralMapping = new Dictionary + { + {"00020001", (1, 1)}, + {"00800040", (10, 10)}, + {"10000800", (2, 20)}, + {"00200010", (3, 30)} + }; + + AstralNum = 0; // 星落次数 + AstralType = 0; // 星落安全区类型 + + 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; + } + } + + private void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat(str); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + var msg = @event["Message"].ToString(); + accessory.Method.SendChat($"/e 获得玩家发送的消息:{msg}"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"钢铁"; + dp.Scale = new(3f); + dp.Position = new(86f, 0, 86f); + dp.Delay = 0; + dp.DestoryAt = 2000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "【全局】范式次数记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(26568|26559)$"], userControl: false)] + public void ParadeigmaNumRecord(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + ParadeigmaNum++; + if (ParadeigmaNum == 4) + isStarFall = true; + else + isStarFall = false; // 同时取消转场阶段 + // 真神中,该变量意义仅为控制三重光波的出现延迟 + DebugMsg($"/e [DEBUG] 发现范式次数增加:{ParadeigmaNum}。", accessory); + } + + private void drawBeastCircle(int beastIdx, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"钢铁{beastIdx}"; + dp.Scale = new(15); + dp.Position = BirdOrBeastPos[beastIdx]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = colorPink.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void drawSnakeLine(int[] SnakeIdx, int delay, int destoryAt, ScriptAccessory accessory) + { + DebugMsg($"/e [DEBUG] 发现蛇蛇{SnakeIdx[0]} -> {SnakePosTarget[SnakeIdx[0]]} 与 {SnakeIdx[1]} -> {SnakePosTarget[SnakeIdx[1]]}", accessory); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"直线{SnakeIdx[0]}"; + dp.Scale = new(11); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Position = SnakePos[SnakeIdx[0]]; + dp.TargetPosition = SnakePosTarget[SnakeIdx[0]]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = colorPink.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + dp.Name = $"直线{SnakeIdx[1]}"; + dp.Position = SnakePos[SnakeIdx[1]]; + dp.TargetPosition = SnakePosTarget[SnakeIdx[1]]; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + private void drawFan(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"三角秘纹{sid}"; + dp.Scale = new(60); + dp.Radian = float.Pi / 3; + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + private void drawHalfCleave(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"半场秘纹{sid}"; + dp.Scale = new(42, 21); + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + private void drawLine(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"直线秘纹{sid}"; + dp.Scale = new(16, 42); + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "分摊标记", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:013C"])] + public void GenerateTargetRecord(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + DebugMsg($"/e 检测到 {tid} 被点名分摊。", accessory); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"分摊{tid}"; + dp.Scale = new(5); + dp.Owner = tid; + dp.Delay = 0; + dp.DestoryAt = 12000; + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "秘纹(三角、半场、直线)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:regex:^(1371[456])$"])] + public void Exoterikos(Event @event, ScriptAccessory accessory) + { + lock (this) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + // 如果EsoterikosSourceIds中没有sid,将其加入; + // 如果EsoterikosSourceIds有sid,将其移出。 + + if (EsoterikosSourceIds.Contains(sid)) + { + DebugMsg($"/e [DEBUG] 删除秘纹:{sid}。", accessory); + EsoterikosSourceIds.Remove(sid); + accessory.Method.RemoveDraw(@$"(直线|三角|半场)秘纹{sid}"); + return; + } + else + EsoterikosSourceIds.Add(sid); + + var sdid = JsonConvert.DeserializeObject(@event["SourceDataId"]); + + DebugMsg($"/e [DEBUG] 发现秘纹{sid}正在释放:{sdid}。", accessory); + + switch (sdid) + { + case 13714: + if (isStarFall) + drawLine(sid, 4000, 25000, accessory); + else + drawLine(sid, 0, 25000, accessory); + break; + case 13715: + drawHalfCleave(sid, 0, 25000, accessory); + break; + case 13716: + drawFan(sid, 0, 25000, accessory); + break; + default: + return; + } + } + } + + [ScriptMethod(name: "悼念", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26601"], userControl: false)] + public void OrbsDownSync(Event @event, ScriptAccessory accessory) + { + Task.Delay(50).ContinueWith(t => + { + // 避免该阶段有某一波秘纹没放出来就被压掉了 + EsoterikosSourceIds = []; + // 进入转场阶段标志 + isStarFall = true; + accessory.Method.RemoveDraw(".*"); + }); + } + + [ScriptMethod(name: "痛苦(斜向冲锋)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26579"])] + public void Algedon(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"痛苦{sid}"; + dp.Scale = new(30, 60); + dp.Owner = sid; + dp.Delay = 0; + dp.DestoryAt = 8000; + dp.Color = colorPink.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "不义(小拳拳)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26584"])] + public void Adikia(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"不义{sid}"; + dp.Scale = new(21); + dp.Position = new Vector3(121.0f, 0, 100.0f); + dp.Delay = 0; + dp.DestoryAt = 7500; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp.Position = new Vector3(79.0f, 0, 100.0f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "星蚀(转场星落)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26573"])] + public void Astral(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"星蚀{sid}"; + dp.Scale = new(10); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = sid; + dp.Delay = 0; + dp.DestoryAt = 3000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "贝贝钢铁", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E70", "Id:00200010", "Index:regex:^(0000000[9ABC])$"])] + public async void BeastCircle(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["Index"], out var idx)) return; + + DebugMsg($"/e [DEBUG] 发现贝贝钢铁{idx}。", accessory); + + switch (ParadeigmaNum) + { + case 1: + case 2: + case 3: + drawBeastCircle(getBeastIndex(idx), 0, 20000, accessory); + break; + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + await Task.Delay(6000); + drawBeastCircle(isTurnLeft ? getBeastBirdTurnLeftIndex(getBeastIndex(idx)) : getBeastBirdTurnRightIndex(getBeastIndex(idx)), 0, 20000, accessory); + break; + default: + return; + } + } + + [ScriptMethod(name: "蛇蛇直线", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E70", "Id:00200010", "Index:regex:^(000000(0[DEF]|(1[01234])))$"])] + public async void SnakeLine(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["Index"], out var idx)) return; + DebugMsg($"/e [DEBUG] 发现蛇蛇直线{idx}。", accessory); + + switch (ParadeigmaNum) + { + case 1: + case 2: + case 3: + drawSnakeLine(getSnakeIndex(idx), 0, 20000, accessory); + break; + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + await Task.Delay(6000); + drawSnakeLine(isTurnLeft ? getSnakeTurnLeftIndex(getSnakeIndex(idx)) : getSnakeTurnRightIndex(getSnakeIndex(idx)), 0, 20000, accessory); + break; + default: + return; + } + } + + [ScriptMethod(name: "旋转方向记录(不可控)", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E70", "Id:regex:^(00200010|00020001)$", "Index:00000002"], userControl: false)] + public void RotateRecord(Event @event, ScriptAccessory accessory) + { + var id = @event["Id"].ToString(); + switch (id) + { + case "00200010": + DebugMsg($"/e [DEBUG] 向左转。", accessory); + isTurnLeft = true; + break; + + case "00020001": + DebugMsg($"/e [DEBUG] 向右转。", accessory); + isTurnLeft = false; + break; + + default: + return; + } + } + + [ScriptMethod(name: "贝贝钢铁消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26191"], userControl: false)] + public void BeastCircleRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(@$"钢铁\d+$"); + accessory.Method.RemoveDraw(@$"四象限\d+$"); + } + + [ScriptMethod(name: "蛇蛇直线消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26192"], userControl: false)] + public void SnakeLineRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(@$"直线\d+$"); + // 顺便删除火线 + accessory.Method.RemoveDraw(@$"四象限\d+$"); + } + + private int getBeastIndex(uint index) + { + switch (index) + { + case 9: + return 0; + case 10: + return 1; + case 11: + return 2; + case 12: + return 3; + default: + return -1; + } + } + private int getBeastBirdTurnRightIndex(int posIndex) + { + switch (posIndex) + { + case 0: + return 1; + case 1: + return 3; + case 2: + return 0; + case 3: + return 2; + default: + return -1; + } + } + private int getBeastBirdTurnLeftIndex(int posIndex) + { + switch (posIndex) + { + case 0: + return 2; + case 1: + return 0; + case 2: + return 3; + case 3: + return 1; + default: + return -1; + } + } + + private int[] getSnakeIndex(uint index) + { + // 80034E70 00200010 0000000D 00000000 上 一三列 蛇蛇出现 0x0D = 13 + // 80034E70 00200010 0000000E 00000000 上 二四列 蛇蛇出现 0x0E = 14 + // 80034E70 00200010 0000000F 00000000 下 一三列 蛇蛇出现 0x0F = 15 + // 80034E70 00200010 00000010 00000000 下 二四列 蛇蛇出现 0x10 = 16 + // 80034E70 00200010 00000011 00000000 左 一三列 蛇蛇出现 0x11 = 17 + // 80034E70 00200010 00000012 00000000 左 二四列 蛇蛇出现 0x12 = 18 + // 80034E70 00200010 00000013 00000000 右 一三列 蛇蛇出现 0x13 = 19 + // 80034E70 00200010 00000014 00000000 右 二四列 蛇蛇出现 0x14 = 20 + + switch (index) + { + case 13: + return [0, 2]; + case 14: + return [1, 3]; + case 15: + return [11, 9]; + case 16: + return [10, 8]; + case 17: + return [15, 13]; + case 18: + return [14, 12]; + case 19: + return [4, 6]; + case 20: + return [5, 7]; + default: + return [-1, -1]; + } + } + + private int[] getSnakeTurnRightIndex(int[] posIndex) + { + return [posIndex[0] + 4 > 15 ? posIndex[0] + 4 - 16 : posIndex[0] + 4, + posIndex[1] + 4 > 15 ? posIndex[1] + 4 - 16 : posIndex[1] + 4]; + } + + private int[] getSnakeTurnLeftIndex(int[] posIndex) + { + return [posIndex[0] - 4 < 0 ? posIndex[0] - 4 + 16 : posIndex[0] - 4, + posIndex[1] - 4 < 0 ? posIndex[1] - 4 + 16 : posIndex[1] - 4]; + } + + #region EnvControl记录 + + // DirectorId不变,似乎与地图相关 + // State/Id与动作特效相关 + // Index即“哪一个” + // Param不晓得 + + // State/Id + // 00200010 出现 + // 20001000 施法中 + // 40000004 跑路 + + // DirectorId State/Id Index Param + // 80034E70 00200010 00000015 00000000 左上 鸟鸟出现 0x15 = 21 + // 80034E70 00200010 00000016 00000000 右上 鸟鸟出现 0x16 = 22 + // 80034E70 00200010 00000017 00000000 左下 鸟鸟出现 0x17 = 23 + // 80034E70 00200010 00000018 00000000 右下 鸟鸟出现 0x18 = 24 + + // 80034E70 00200010 00000009 00000000 左上 贝贝出现 0x09 = 9 + // 80034E70 00200010 0000000A 00000000 右上 贝贝出现 0x0A = 10 + // 80034E70 00200010 0000000B 00000000 左下 贝贝出现 0x0B = 11 + // 80034E70 00200010 0000000C 00000000 右下 贝贝出现 0x0C = 12 + + // 80034E70 00200010 0000000D 00000000 上 一三列 蛇蛇出现 0x0D = 13 + // 80034E70 00200010 0000000E 00000000 上 二四列 蛇蛇出现 0x0E = 14 + // 80034E70 00200010 0000000F 00000000 下 一三列 蛇蛇出现 0x0F = 15 + // 80034E70 00200010 00000010 00000000 下 二四列 蛇蛇出现 0x10 = 16 + // 80034E70 00200010 00000011 00000000 左 一三列 蛇蛇出现 0x11 = 17 + // 80034E70 00200010 00000012 00000000 左 二四列 蛇蛇出现 0x12 = 18 + // 80034E70 00200010 00000013 00000000 右 一三列 蛇蛇出现 0x13 = 19 + // 80034E70 00200010 00000014 00000000 右 二四列 蛇蛇出现 0x14 = 20 + + // 80034E70 20001000 00000015 00000000 鸟鸟准备施法月环 + // 80034E70 20001000 00000016 00000000 鸟鸟准备施法月环 + // 80034E70 20001000 00000017 00000000 鸟鸟准备施法月环 + // 80034E70 20001000 00000018 00000000 鸟鸟准备施法月环 + + // 80034E70 40000004 00000015 00000000 鸟鸟施法完毕跑路 + // 80034E70 40000004 00000016 00000000 鸟鸟施法完毕跑路 + // 80034E70 40000004 00000017 00000000 鸟鸟施法完毕跑路 + // 80034E70 40000004 00000018 00000000 鸟鸟施法完毕跑路 + + // 星极超流 + // 80034E70 00200010 00000002 00000000 往左转 箭头 + // 80034E70 00020001 00000002 00000000 往右转 箭头 + // 00400004 00000002 00000000 删除箭头 + // 00400004 00000004 00000000 真特效 转盘子 往左转 + + // 00020001 00000005 00000000 星极超流 右上到左下的火线 + // 00400020 00000005 00000000 星极超流 左上到右下的火线 + + // 00010001 00000005 00000000 星极超流 右上到左下的火线 + // 00200010 00000005 00000000 星极超流 左上到右下的火线 + // 00400004 00000005 00000000 星极超流 左上到右下的火线 + + + // 00020001 00000001 00000000 星极超流地板花纹 + // 00010001 00000003 00000000 ? + + // 80034E70 00200010 00000006 00000000 星落 左 星星排列1 + // 00400004 00000006 星落 左 星星排列2 + // 00800040 00000006 + // 10000800 00000006 + // 00080004 00000006 星落 左 星星消失 + + + + // 80034E70 00200010 00000007 00000000 星落 下 星星排列1 + // 80034E70 00200010 00000008 00000000 星落 右 星星排列1 + // 80034E70 00200010 00000009 00000000 左上 贝贝出现 + + // 00080004 00000001 00000000 星极超流地板花纹 + + #endregion +} diff --git a/lianying1997/06-EndWalker/ZodiarkEx.cs b/lianying1997/06-EndWalker/ZodiarkEx.cs new file mode 100644 index 0000000..3809146 --- /dev/null +++ b/lianying1997/06-EndWalker/ZodiarkEx.cs @@ -0,0 +1,878 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Network.Structures.InfoProxy; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs; +using KodakkuAssist.Module.Script.Type; + +namespace UsamisScript.EndWalker.ZodiarkEx; + +[ScriptType(name: "Zodiark-Ex [佐迪亚克暝暗歼灭战]", territorys: [993], + guid: "e24a0c8b-5c41-4e58-87c3-355f1f925986", version: "0.0.0.6", author: "Usami", note: noteStr)] +public class ZodiarkEx +{ + const string noteStr = + """ + v0.0.0.6: + 鸭门。 + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public bool DebugMode { get; set; } = false; + + // TODO + // 加一个阶段指示,转场。转场的秘纹绘图时间调整一下 + // 以及,星落部分,不要让画面过乱。根据地图特效直接绘出星落安全区。 + + int ParadeigmaNum = 0; // 范式次数 + Vector3[] BirdOrBeastPos = new Vector3[4]; // Quetzalcoatl & Behemoth,记录四个角落月环鸟鸟/钢铁贝贝的位置 + Vector3[] SnakePos = new Vector3[16]; // Python,记录十六个区域蛇蛇直线的位置 + Vector3[] SnakePosTarget = new Vector3[16]; // Python,记录十六个区域蛇蛇最后目标的位置 + List EsoterikosSourceIds = []; // 已记录的秘纹ID + public ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1f, 0f, 0f, 1.0f) }; + bool isTurnLeft = false; // 爸爸转哪儿 + bool isStarFall = false; // 转场阶段,为三重秘纹绘图 + Dictionary? AstralMapping = null; + int AstralNum = 0; // 星落次数 + int AstralType = 0; // 星落安全区类型 + Vector3[] AstralSafePos = new Vector3[6]; // 星落6个安全点 + public void Init(ScriptAccessory accessory) + { + ParadeigmaNum = 0; // 范式次数 + + // 记录内场角位置 + BirdOrBeastPos[0] = new(89.50f, 0, 89.50f); // 左上 + BirdOrBeastPos[1] = new(110.50f, 0, 89.50f); // 右上 + BirdOrBeastPos[2] = new(89.50f, 0, 110.50f); // 左下 + BirdOrBeastPos[3] = new(110.50f, 0, 110.50f); // 右下 + + // 蛇的16个位置,从左上开始顺时针 + SnakePos[0] = new(85.00f, 0, 75.00f); + SnakePos[1] = new(95.00f, 0, 75.00f); + SnakePos[2] = new(105.00f, 0, 75.00f); + SnakePos[3] = new(115.00f, 0, 75.00f); + + SnakePos[4] = new(125.00f, 0, 85.00f); + SnakePos[5] = new(125.00f, 0, 95.00f); + SnakePos[6] = new(125.00f, 0, 105.00f); + SnakePos[7] = new(125.00f, 0, 115.00f); + + SnakePos[8] = new(115.00f, 0, 125.00f); + SnakePos[9] = new(105.00f, 0, 125.00f); + SnakePos[10] = new(95.00f, 0, 125.00f); + SnakePos[11] = new(85.00f, 0, 125.00f); + + SnakePos[12] = new(75.00f, 0, 115.00f); + SnakePos[13] = new(75.00f, 0, 105.00f); + SnakePos[14] = new(75.00f, 0, 95.00f); + SnakePos[15] = new(75.00f, 0, 85.00f); + + // 蛇的16个目标位置 + SnakePosTarget[0] = SnakePos[11]; + SnakePosTarget[1] = SnakePos[10]; + SnakePosTarget[2] = SnakePos[9]; + SnakePosTarget[3] = SnakePos[8]; + SnakePosTarget[4] = SnakePos[15]; + SnakePosTarget[5] = SnakePos[14]; + SnakePosTarget[6] = SnakePos[13]; + SnakePosTarget[7] = SnakePos[12]; + SnakePosTarget[8] = SnakePos[3]; + SnakePosTarget[9] = SnakePos[2]; + SnakePosTarget[10] = SnakePos[1]; + SnakePosTarget[11] = SnakePos[0]; + SnakePosTarget[12] = SnakePos[7]; + SnakePosTarget[13] = SnakePos[6]; + SnakePosTarget[14] = SnakePos[5]; + SnakePosTarget[15] = SnakePos[4]; + + // 星落6个安全点 + AstralSafePos[0] = new(86, 0, 86); + AstralSafePos[1] = new(100, 0, 86); + AstralSafePos[2] = new(114, 0, 86); + AstralSafePos[3] = new(86, 0, 100); + AstralSafePos[4] = new(100, 0, 100); + AstralSafePos[5] = new(114, 0, 100); + + EsoterikosSourceIds = []; // 已记录的秘纹ID + + isTurnLeft = false; // 爸爸转哪儿 + isStarFall = false; // 转场阶段,为三重秘纹绘图 + + // 星落字典 + AstralMapping = new Dictionary + { + {"00020001", (1, 1)}, + {"00800040", (10, 10)}, + {"10000800", (2, 20)}, + {"00200010", (3, 30)} + }; + + AstralNum = 0; // 星落次数 + AstralType = 0; // 星落安全区类型 + + 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; + } + } + + private void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat(str); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + var msg = @event["Message"].ToString(); + accessory.Method.SendChat($"/e 获得玩家发送的消息:{msg}"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"钢铁"; + dp.Scale = new(3f); + dp.Position = new(86f, 0, 86f); + dp.Delay = 0; + dp.DestoryAt = 2000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "【全局】范式次数记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26559"], userControl: false)] + public void ParadeigmaNumRecord(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + ParadeigmaNum++; + isStarFall = false; // 同时取消转场阶段 + DebugMsg($"/e [DEBUG] 发现范式次数增加:{ParadeigmaNum}。", accessory); + } + private void drawBirdDonut(int birdIdx, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"月环{birdIdx}"; + dp.Scale = new(15); + dp.InnerScale = new(5); + dp.Radian = float.Pi * 2; + dp.Position = BirdOrBeastPos[birdIdx]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + + private void drawBeastCircle(int beastIdx, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"钢铁{beastIdx}"; + dp.Scale = new(15); + dp.Position = BirdOrBeastPos[beastIdx]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void drawSnakeLine(int[] SnakeIdx, int delay, int destoryAt, ScriptAccessory accessory) + { + DebugMsg($"/e [DEBUG] 发现蛇蛇{SnakeIdx[0]} -> {SnakePosTarget[SnakeIdx[0]]} 与 {SnakeIdx[1]} -> {SnakePosTarget[SnakeIdx[1]]}", accessory); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"直线{SnakeIdx[0]}"; + dp.Scale = new(11); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Position = SnakePos[SnakeIdx[0]]; + dp.TargetPosition = SnakePosTarget[SnakeIdx[0]]; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + dp.Name = $"直线{SnakeIdx[1]}"; + dp.Position = SnakePos[SnakeIdx[1]]; + dp.TargetPosition = SnakePosTarget[SnakeIdx[1]]; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + private void drawFan(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"三角秘纹{sid}"; + dp.Scale = new(60); + dp.Radian = float.Pi / 3; + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + private void drawHalfCleave(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"半场秘纹{sid}"; + dp.Scale = new(42, 21); + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + private void drawLine(uint sid, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"直线秘纹{sid}"; + dp.Scale = new(16, 42); + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "分摊标记", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:013C"])] + public void GenerateTargetRecord(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["TargetId"], out var tid)) return; + DebugMsg($"/e 检测到 {tid} 被点名分摊。", accessory); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"分摊{tid}"; + dp.Scale = new(5); + dp.Owner = tid; + dp.Delay = 0; + dp.DestoryAt = 12000; + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "秘纹(三角、半场、直线)", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:regex:^(1371[123])$"])] + public void Exoterikos(Event @event, ScriptAccessory accessory) + { + lock (this) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + + // 如果EsoterikosSourceIds中没有sid,将其加入; + // 如果EsoterikosSourceIds有sid,将其移出。 + + if (EsoterikosSourceIds.Contains(sid)) + { + DebugMsg($"/e [DEBUG] 删除秘纹:{sid}。", accessory); + EsoterikosSourceIds.Remove(sid); + accessory.Method.RemoveDraw(@$"(直线|三角|半场)秘纹{sid}"); + return; + } + else + EsoterikosSourceIds.Add(sid); + + var sdid = JsonConvert.DeserializeObject(@event["SourceDataId"]); + + DebugMsg($"/e [DEBUG] 发现秘纹{sid}正在释放:{sdid}。", accessory); + + switch (sdid) + { + case 13711: + if (isStarFall) + drawLine(sid, 5000, 25000, accessory); + else + drawLine(sid, 0, 25000, accessory); + break; + case 13712: + drawHalfCleave(sid, 0, 25000, accessory); + break; + case 13713: + drawFan(sid, 0, 25000, accessory); + break; + default: + return; + } + } + } + + [ScriptMethod(name: "悼念", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26601"], userControl: false)] + public void OrbsDownSync(Event @event, ScriptAccessory accessory) + { + Task.Delay(50).ContinueWith(t => + { + // 避免该阶段有某一波秘纹没放出来就被压掉了 + EsoterikosSourceIds = []; + // 进入转场阶段标志 + isStarFall = true; + accessory.Method.RemoveDraw(".*"); + }); + } + + [ScriptMethod(name: "痛苦(斜向冲锋)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26606"])] + public void Algedon(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"痛苦{sid}"; + dp.Scale = new(30, 60); + dp.Owner = sid; + dp.Delay = 0; + dp.DestoryAt = 8000; + dp.Color = colorPink.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "不义(小拳拳)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26609"])] + public void Adikia(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"不义{sid}"; + dp.Scale = new(21); + dp.Position = new Vector3(121.0f, 0, 100.0f); + dp.Delay = 0; + dp.DestoryAt = 7500; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp.Position = new Vector3(79.0f, 0, 100.0f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "星蚀(转场星落)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:26599"])] + public void Astral(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["SourceId"], out var sid)) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"星蚀{sid}"; + dp.Scale = new(10); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = sid; + dp.Delay = 0; + dp.DestoryAt = 3000; + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "鸟鸟月环", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E71", "Id:00200010", "Index:regex:^(0000001[5678])$"])] + public async void BirdDonut(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["Index"], out var idx)) return; + + DebugMsg($"/e [DEBUG] 发现鸟鸟月环{idx}。", accessory); + + switch (ParadeigmaNum) + { + case 1: + case 2: + drawBirdDonut(getBirdIndex(idx), 0, 20000, accessory); + break; + case 3: + case 4: + case 5: + case 6: + await Task.Delay(5000); + drawBirdDonut(isTurnLeft ? getBeastBirdTurnLeftIndex(getBirdIndex(idx)) : getBeastBirdTurnRightIndex(getBirdIndex(idx)), 0, 20000, accessory); + break; + case 7: + case 8: + case 9: + await Task.Delay(9500); + drawBirdDonut(isTurnLeft ? getBeastBirdTurnLeftIndex(getBirdIndex(idx)) : getBeastBirdTurnRightIndex(getBirdIndex(idx)), 0, 20000, accessory); + break; + default: + return; + } + } + + [ScriptMethod(name: "贝贝钢铁", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E71", "Id:00200010", "Index:regex:^(0000000[9ABC])$"])] + public async void BeastCircle(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["Index"], out var idx)) return; + + DebugMsg($"/e [DEBUG] 发现贝贝钢铁{idx}。", accessory); + + switch (ParadeigmaNum) + { + case 1: + case 2: + drawBeastCircle(getBeastIndex(idx), 0, 20000, accessory); + break; + case 3: + case 4: + case 5: + case 6: + await Task.Delay(5000); + drawBeastCircle(isTurnLeft ? getBeastBirdTurnLeftIndex(getBeastIndex(idx)) : getBeastBirdTurnRightIndex(getBeastIndex(idx)), 0, 20000, accessory); + break; + case 7: + case 8: + case 9: + await Task.Delay(9500); + drawBeastCircle(isTurnLeft ? getBeastBirdTurnLeftIndex(getBeastIndex(idx)) : getBeastBirdTurnRightIndex(getBeastIndex(idx)), 0, 20000, accessory); + break; + default: + return; + } + } + + [ScriptMethod(name: "蛇蛇直线", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E71", "Id:00200010", "Index:regex:^(000000(0[DEF]|(1[01234])))$"])] + public async void SnakeLine(Event @event, ScriptAccessory accessory) + { + if (!ParseObjectId(@event["Index"], out var idx)) return; + DebugMsg($"/e [DEBUG] 发现蛇蛇直线{idx}。", accessory); + + switch (ParadeigmaNum) + { + case 1: + case 2: + drawSnakeLine(getSnakeIndex(idx), 0, 20000, accessory); + break; + case 3: + await Task.Delay(9500); + drawSnakeLine(isTurnLeft ? getSnakeTurnLeftIndex(getSnakeIndex(idx)) : getSnakeTurnRightIndex(getSnakeIndex(idx)), 0, 20000, accessory); + break; + case 4: + drawSnakeLine(getSnakeIndex(idx), 0, 20000, accessory); + break; + case 5: + case 6: + await Task.Delay(5000); + drawSnakeLine(isTurnLeft ? getSnakeTurnLeftIndex(getSnakeIndex(idx)) : getSnakeTurnRightIndex(getSnakeIndex(idx)), 0, 20000, accessory); + break; + case 7: + await Task.Delay(9500); + drawSnakeLine(isTurnLeft ? getSnakeTurnLeftIndex(getSnakeIndex(idx)) : getSnakeTurnRightIndex(getSnakeIndex(idx)), 0, 20000, accessory); + break; + case 8: + case 9: + await Task.Delay(9500); + drawSnakeLine(isTurnLeft ? getSnakeTurnLeftIndex(getSnakeIndex(idx)) : getSnakeTurnRightIndex(getSnakeIndex(idx)), 0, 20000, accessory); + break; + default: + return; + } + } + + [ScriptMethod(name: "旋转方向记录(不可控)", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E71", "Id:regex:^(00200010|00020001)$", "Index:00000002"], userControl: false)] + public void RotateRecord(Event @event, ScriptAccessory accessory) + { + var id = @event["Id"].ToString(); + switch (id) + { + case "00200010": + DebugMsg($"/e [DEBUG] 向左转。", accessory); + isTurnLeft = true; + break; + + case "00020001": + DebugMsg($"/e [DEBUG] 向右转。", accessory); + isTurnLeft = false; + break; + + default: + return; + } + } + + [ScriptMethod(name: "星蚀指路", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E71", "Index:regex:^(0000000[678])$"])] + public void AstralDir(Event @event, ScriptAccessory accessory) + { + // pattern 1: W, mid, NE (00020001, 00200010, 00800040) + // pattern 2: NW, W, N (10000800, 00800040, 00020001) + // pattern 3: W, N, mid (00020001, 10000800, 00200010) + // pattern 4: NW, mid, NE (10000800, 00200010, 00800040) + // pattern 5: NW, N, NE (00200010, 10000800, 00800040) + // pattern 6: NW, W, N (00200010, 00800040, 00020001) + + if (AstralNum == 2) return; + AstralNum++; + var id = @event["Id"].ToString(); + if (AstralMapping.ContainsKey(id)) + { + var (val1, val2) = AstralMapping[id]; + AstralType = AstralType + (AstralNum == 1 ? val1 : val2); + } + + if (AstralNum != 2) return; + DebugMsg($"/e 获得AstralType: {AstralType}", accessory); + switch (AstralType) + { + case 31: + drawAstralDir(3, 4, 2, accessory); + break; + case 12: + drawAstralDir(0, 3, 1, accessory); + break; + case 21: + drawAstralDir(3, 1, 4, accessory); + break; + case 32: + drawAstralDir(0, 4, 2, accessory); + break; + case 23: + drawAstralDir(0, 1, 2, accessory); + break; + case 13: + drawAstralDir(0, 3, 1, accessory); + break; + default: + break; + } + } + + private void drawAstralDir(int safeIdx1, int safeIdx2, int safeIdx3, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"星落安全区1"; + dp.Scale = new(3f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Position = AstralSafePos[safeIdx1]; + dp.Delay = 0; + dp.DestoryAt = 11000; + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp.Name = $"星落安全区2"; + dp.Position = AstralSafePos[safeIdx2]; + dp.Delay = 0; + dp.DestoryAt = 15000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp.Name = $"星落安全区3"; + dp.Position = AstralSafePos[safeIdx3]; + dp.Delay = 0; + dp.DestoryAt = 19000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"星落指路标记1"; + dp.Scale = new(0.5f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = AstralSafePos[safeIdx1]; + dp.Delay = 0; + dp.DestoryAt = 11000; + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + dp.Name = $"星落指路标记2"; + dp.TargetPosition = AstralSafePos[safeIdx2]; + dp.Delay = 11000; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + dp.Name = $"星落指路标记2"; + dp.TargetPosition = AstralSafePos[safeIdx3]; + dp.Delay = 15000; + dp.DestoryAt = 4000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + } + + [ScriptMethod(name: "火线安全区", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:80034E71", "Id:regex:^(00020001|00400020)$", "Index:00000005"])] + public async void Firebar(Event @event, ScriptAccessory accessory) + { + await Task.Delay(500); + var id = @event["Id"].ToString(); + switch (id) + { + case "00400020": + DebugMsg($"/e [DEBUG] 检测到左上到右下的火线。", accessory); + + if (isTurnLeft) + { + drawQuadrant(1, 0, 20000, accessory); + drawQuadrant(3, 0, 20000, accessory); + } + else + { + drawQuadrant(0, 0, 20000, accessory); + drawQuadrant(2, 0, 20000, accessory); + } + break; + case "00020001": + DebugMsg($"/e [DEBUG] 检测到右上到左下的火线。", accessory); + if (isTurnLeft) + { + drawQuadrant(0, 0, 20000, accessory); + drawQuadrant(2, 0, 20000, accessory); + } + else + { + drawQuadrant(1, 0, 20000, accessory); + drawQuadrant(3, 0, 20000, accessory); + } + break; + default: + return; + } + } + + private void drawQuadrant(int posIdx, int delay, int destoryAt, ScriptAccessory accessory) + { + Vector3 bias; + switch (posIdx) + { + // rotation中0居然是下方,dp的rotation是从“下”逆时针转动。 + case 0: + bias = new Vector3(0, 0, -1.5f); + break; + case 1: + bias = new Vector3(-1.5f, 0, 0); + break; + case 2: + bias = new Vector3(0, 0, 1.5f); + break; + case 3: + bias = new Vector3(1.5f, 0, 0); + break; + default: + return; + } + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"四象限{posIdx}"; + dp.Scale = new(30); + dp.Radian = float.Pi / 2; + dp.Position = new Vector3(100, 0, 100) + bias; + dp.Rotation = float.Pi / 2 * posIdx; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = colorRed.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "鸟鸟月环消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26593"], userControl: false)] + public void BirdDonutRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(@$"月环\d+$"); + accessory.Method.RemoveDraw(@$"四象限\d+$"); + } + + [ScriptMethod(name: "贝贝钢铁消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26594"], userControl: false)] + public void BeastCircleRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(@$"钢铁\d+$"); + accessory.Method.RemoveDraw(@$"四象限\d+$"); + } + + [ScriptMethod(name: "蛇蛇直线消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:26595"], userControl: false)] + public void SnakeLineRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(@$"直线\d+$"); + // 顺便删除火线 + accessory.Method.RemoveDraw(@$"四象限\d+$"); + } + + private int getBirdIndex(uint index) + { + switch (index) + { + case 21: + return 0; + case 22: + return 1; + case 23: + return 2; + case 24: + return 3; + default: + return -1; + } + } + + private int getBeastIndex(uint index) + { + switch (index) + { + case 9: + return 0; + case 10: + return 1; + case 11: + return 2; + case 12: + return 3; + default: + return -1; + } + } + + private int getBeastBirdTurnRightIndex(int posIndex) + { + switch (posIndex) + { + case 0: + return 1; + case 1: + return 3; + case 2: + return 0; + case 3: + return 2; + default: + return -1; + } + } + + private int getBeastBirdTurnLeftIndex(int posIndex) + { + switch (posIndex) + { + case 0: + return 2; + case 1: + return 0; + case 2: + return 3; + case 3: + return 1; + default: + return -1; + } + } + + private int[] getSnakeIndex(uint index) + { + // 80034E71 00200010 0000000D 00000000 上 一三列 蛇蛇出现 0x0D = 13 + // 80034E71 00200010 0000000E 00000000 上 二四列 蛇蛇出现 0x0E = 14 + // 80034E71 00200010 0000000F 00000000 下 一三列 蛇蛇出现 0x0F = 15 + // 80034E71 00200010 00000010 00000000 下 二四列 蛇蛇出现 0x10 = 16 + // 80034E71 00200010 00000011 00000000 左 一三列 蛇蛇出现 0x11 = 17 + // 80034E71 00200010 00000012 00000000 左 二四列 蛇蛇出现 0x12 = 18 + // 80034E71 00200010 00000013 00000000 右 一三列 蛇蛇出现 0x13 = 19 + // 80034E71 00200010 00000014 00000000 右 二四列 蛇蛇出现 0x14 = 20 + + switch (index) + { + case 13: + return [0, 2]; + case 14: + return [1, 3]; + case 15: + return [11, 9]; + case 16: + return [10, 8]; + case 17: + return [15, 13]; + case 18: + return [14, 12]; + case 19: + return [4, 6]; + case 20: + return [5, 7]; + default: + return [-1, -1]; + } + } + + private int[] getSnakeTurnRightIndex(int[] posIndex) + { + return [posIndex[0] + 4 > 15 ? posIndex[0] + 4 - 16 : posIndex[0] + 4, + posIndex[1] + 4 > 15 ? posIndex[1] + 4 - 16 : posIndex[1] + 4]; + } + + private int[] getSnakeTurnLeftIndex(int[] posIndex) + { + return [posIndex[0] - 4 < 0 ? posIndex[0] - 4 + 16 : posIndex[0] - 4, + posIndex[1] - 4 < 0 ? posIndex[1] - 4 + 16 : posIndex[1] - 4]; + } + + #region EnvControl记录 + + // DirectorId不变,似乎与地图相关 + // State/Id与动作特效相关 + // Index即“哪一个” + // Param不晓得 + + // State/Id + // 00200010 出现 + // 20001000 施法中 + // 40000004 跑路 + + // DirectorId State/Id Index Param + // 80034E71 00200010 00000015 00000000 左上 鸟鸟出现 0x15 = 21 + // 80034E71 00200010 00000016 00000000 右上 鸟鸟出现 0x16 = 22 + // 80034E71 00200010 00000017 00000000 左下 鸟鸟出现 0x17 = 23 + // 80034E71 00200010 00000018 00000000 右下 鸟鸟出现 0x18 = 24 + + // 80034E71 00200010 00000009 00000000 左上 贝贝出现 0x09 = 9 + // 80034E71 00200010 0000000A 00000000 右上 贝贝出现 0x0A = 10 + // 80034E71 00200010 0000000B 00000000 左下 贝贝出现 0x0B = 11 + // 80034E71 00200010 0000000C 00000000 右下 贝贝出现 0x0C = 12 + + // 80034E71 00200010 0000000D 00000000 上 一三列 蛇蛇出现 0x0D = 13 + // 80034E71 00200010 0000000E 00000000 上 二四列 蛇蛇出现 0x0E = 14 + // 80034E71 00200010 0000000F 00000000 下 一三列 蛇蛇出现 0x0F = 15 + // 80034E71 00200010 00000010 00000000 下 二四列 蛇蛇出现 0x10 = 16 + // 80034E71 00200010 00000011 00000000 左 一三列 蛇蛇出现 0x11 = 17 + // 80034E71 00200010 00000012 00000000 左 二四列 蛇蛇出现 0x12 = 18 + // 80034E71 00200010 00000013 00000000 右 一三列 蛇蛇出现 0x13 = 19 + // 80034E71 00200010 00000014 00000000 右 二四列 蛇蛇出现 0x14 = 20 + + // 80034E71 20001000 00000015 00000000 鸟鸟准备施法月环 + // 80034E71 20001000 00000016 00000000 鸟鸟准备施法月环 + // 80034E71 20001000 00000017 00000000 鸟鸟准备施法月环 + // 80034E71 20001000 00000018 00000000 鸟鸟准备施法月环 + + // 80034E71 40000004 00000015 00000000 鸟鸟施法完毕跑路 + // 80034E71 40000004 00000016 00000000 鸟鸟施法完毕跑路 + // 80034E71 40000004 00000017 00000000 鸟鸟施法完毕跑路 + // 80034E71 40000004 00000018 00000000 鸟鸟施法完毕跑路 + + // 星极超流 + // 80034E71 00200010 00000002 00000000 往左转 箭头 + // 80034E71 00020001 00000002 00000000 往右转 箭头 + // 00400004 00000002 00000000 删除箭头 + // 00400004 00000004 00000000 真特效 转盘子 往左转 + + // 00020001 00000005 00000000 星极超流 右上到左下的火线 + // 00400020 00000005 00000000 星极超流 左上到右下的火线 + + // 00010001 00000005 00000000 星极超流 右上到左下的火线 + // 00200010 00000005 00000000 星极超流 左上到右下的火线 + // 00400004 00000005 00000000 星极超流 左上到右下的火线 + + + // 00020001 00000001 00000000 星极超流地板花纹 + // 00010001 00000003 00000000 ? + + // 80034E71 00200010 00000006 00000000 星落 左 星星排列1 + // 00400004 00000006 星落 左 星星排列2 + // 00800040 00000006 + // 10000800 00000006 + // 00080004 00000006 星落 左 星星消失 + + + + // 80034E71 00200010 00000007 00000000 星落 下 星星排列1 + // 80034E71 00200010 00000008 00000000 星落 右 星星排列1 + // 80034E71 00200010 00000009 00000000 左上 贝贝出现 + + // 00080004 00000001 00000000 星极超流地板花纹 + + #endregion +} diff --git a/lianying1997/06-EndWalker/p12s.cs b/lianying1997/06-EndWalker/p12s.cs new file mode 100644 index 0000000..79f123e --- /dev/null +++ b/lianying1997/06-EndWalker/p12s.cs @@ -0,0 +1,3284 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Data; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs; +using KodakkuAssist.Module.Script.Type; + +namespace UsamisScript.EndWalker.p12s; + +[ScriptType(name: "P12S [零式万魔殿 荒天之狱4]", territorys: [1154], guid: "563bd710-59b8-46de-bbac-f1527d7c0803", version: "0.0.0.11", author: "Usami", note: noteStr, updateInfo: UpdateInfo)] + +public class p12s +{ + const string noteStr = + """ + 请先按需求检查并设置“用户设置”栏目。 + 门神到超链后对话,本体到黑白塔。 + 鸭门。 + """; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public static bool DebugMode { get; set; } = false; + + public enum PD1StrategyEnum + { + 正攻_Regular, + 无敌_Invuln, + 无敌改_InvulnEx, + } + + [UserSetting("范式一解法")] + public PD1StrategyEnum PD1Strategy { get; set; } = PD1StrategyEnum.正攻_Regular; + + [UserSetting("站位提示圈绘图-普通颜色")] + public static ScriptColor posColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public static ScriptColor posColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("地火(宇宙火劫)爆炸区颜色")] + public ScriptColor exflareColor { get; set; } = new ScriptColor { V4 = new Vector4(0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("地火(宇宙火劫)预警区颜色")] + public ScriptColor exflareWarnColor { get; set; } = new ScriptColor { V4 = new Vector4(0f, 0.3f, 0.6f, 1.0f) }; + public enum P12S_Phase + { + Init, // 初始 + Paradeigma_I, // 一范 + Paradeigma_II, // 二范 + Paradeigma_III, // 三范 + SuperChain_I, // 超链I + Gaia_I, // 小世界一 + Classic_I, // 一索尼 + Caloric_I, // 一风火 + Exflare, // 地火 + Pangenesis, // 泛生论黑白塔 + Classic_II, // 二索尼 + Caloric_II, // 二风火 + Gaia_II, // 小世界二 + } + + // 竞态锁 + readonly object db_PD1_lockObject = new object(); + readonly object db_PD2_lockObject = new object(); + readonly object db_SC1_lockObject = new object(); + P12S_Phase phase = P12S_Phase.Init; + List db_isLeftCleave = [false, false, false]; // 门神左右刀记录,是否为左刀 + bool db_PD1_isChecked = false; // 门神一范天使位置是否已记录过 + bool db_PD1_isNorthFirst = false; // 门神一范天使是否先刷新在北方 + bool db_PD1_drawn = false; // 门神一范天使安全区是否已绘制完毕 + bool db_PD2_fromRightBottom = false; // 门神二范,根据北侧天使偏置判断从左下开始还是右下开始 + List db_PD2_shouldWhiteTower = [false, false, false, false]; // 门神二范,根据天使连线颜色判断需要白塔还是黑塔 + int db_PD2_towerRecordNum = 0; // 门神二范已记录塔buff数 + List db_PD2_isChosenTower = [false, false, false, false, false, false, false, false]; // 门神二范,对应玩家是否被点放塔 + List db_PD2_isWhiteTower = [false, false, false, false, false, false, false, false]; // 门神二范,对应玩家是否被点放白塔 + bool db_PD2_drawn = false; // 门神二范,放塔是否已绘制 + List db_SC1_theories = []; // 超链一元素收集 + bool db_SC1_round1_drawn = false; // 超链一第一轮是否绘制完毕 + bool db_SC1_isOut = false; // 超链一第一轮是否为钢铁 + bool db_SC1_isSpread = false; // 超链一第一轮是否为分散 + int db_SC1_myBuff = -1; // 超链一我的Buff + static List db_SC1_BWTBidx = [-1, -1, -1, -1]; // Black White Tower Beam index,四个元素,黑塔-白塔-黑分摊-白分摊 + bool db_SC1_round2_drawn = false; // 超链一第二轮是否绘制完毕 + bool db_SC1_round3_drawn = false; // 超链一第三轮是否绘制完毕 + List mb_Gaia1_dangerPlace = [false, false, false, false, false, false, false, false]; // 小世界一安全角落 + bool mb_Gaia1_dangerPlace_hasDrawn = false; // 小世界一安全角落是否绘制完毕 + List mb_Classic1_playerGroup = [0, 0, 0, 0, 0, 0, 0, 0]; // 一索尼玩家分组 + List mb_Classic1_elements = new List(); // 一索尼元素 + bool mb_Classic1_etDrawn = false; // 一索尼元素指向是否绘制完毕 + bool mb_Classic1_implodeDrawn = false; // 一索尼元素毁灭是否绘制完毕 + bool mb_Classic1_RayDirDrawn = false; // 一索尼射线指路是否绘制完毕 + int mb_Caloric_phase = 0; // 风火分阶段 + List mb_Caloric_isFirstTarget = [false, false, false, false, false, false, false, false]; // 一风火初始分摊目标 + List mb_Caloric_isWind = [false, false, false, false, false, false, false, false]; // 一风火buff + List mb_Caloric_WindPriority = [0, 0, 0, 0, 0, 0, 0, 0]; // 一风火风优先级 + List mb_Caloric_FirePriority = [0, 0, 0, 0, 0, 0, 0, 0]; // 一风火火优先级 + bool mb_Caloric_ParnterStackDirDrawn = false; // 一风火四组分摊指路是否绘制完毕 + bool mb_Caloric_ParnterStackDrawn = false; // 一风火四组分摊范围是否绘制完毕 + bool mb_Caloric_SecondParnterStackDirDrawn = false; // 一风火二次分摊指路是否绘制完毕 + bool mb_Caloric_SecondParnterStackDrawn = false; // 一风火二次分摊范围是否绘制完毕 + bool mb_Caloric_SecondWindDonutDrawn = false; // 一风火环风是否绘制完毕 + List mb_Exflare_FlarePos = [false, false, false, false]; // 地火核爆区 + bool mb_Exflare_DirDrawn = false; // 地火指路是否绘制完毕 + int mb_Pangenesis_towerNum = 0; // 黑白塔出塔计数 + List mb_Pangenesis_phase = [false, false, false, false, false, false, false, false]; // 黑白塔程序阶段处理是否完成 + List mb_Pangenesis_isLong = [false, false, false, false, false, false, false, false]; // 黑白塔,长buff + List mb_Pangenesis_isWhite = [false, false, false, false, false, false, false, false]; // 黑白塔,是白Buff + List mb_Pangenesis_hasFactor = [false, false, false, false, false, false, false, false]; // 黑白塔,有合成因子 + List mb_Pangenesis_isTwo = [false, false, false, false, false, false, false, false]; // 黑白塔,有两层因子 + List mb_Pangenesis_shouldWhiteTower = [false, false, false, false, false, false, false, false]; // 黑白塔,应该踩白塔 + List mb_Pangenesis_TowerIdxOrder = [-1, -1, -1, -1, -1, -1, -1, -1]; // 黑白塔,初始安排的一塔左,一塔左,二塔左上,二塔左下,一塔右,一塔右,二塔右上,二塔右下 + List mb_Pangenesis_TowerIdxSecondOrder = [-1, -1, -1, -1, -1, -1, -1, -1]; // 黑白塔,二塔左上,二塔左上,二塔左下,二塔左下,二塔右上,二塔右上,二塔右下,二塔右下 + public void Init(ScriptAccessory accessory) + { + phase = P12S_Phase.Init; + + db_isLeftCleave = [false, false, false]; // 门神左右刀记录,是否为左刀 + db_PD1_isChecked = false; // 门神一范天使位置是否已记录过 + db_PD1_isNorthFirst = false; // 门神一范天使是否先刷新在北方 + db_PD1_drawn = false; // 门神一范天使安全区是否已绘制完毕 + + db_PD2_fromRightBottom = false; // 门神二范,根据北侧天使偏置判断从左下开始还是右下开始 + db_PD2_shouldWhiteTower = [false, false, false, false]; // 门神二范,根据天使连线颜色判断需要白塔还是黑塔 + db_PD2_towerRecordNum = 0; // 门神二范已记录塔buff数 + db_PD2_isChosenTower = [false, false, false, false, false, false, false, false]; // 门神二范,对应玩家是否被点放塔 + db_PD2_isWhiteTower = [false, false, false, false, false, false, false, false]; // 门神二范,对应玩家是否被点放白塔 + db_PD2_drawn = false; // 门神二范,放塔是否已绘制 + + db_SC1_theories = []; // 超链一元素收集 + db_SC1_round1_drawn = false; // 超链一第一轮是否绘制完毕 + db_SC1_isOut = false; // 超链一第一轮是否为钢铁 + db_SC1_isSpread = false; // 超链一第一轮是否为分散 + + db_SC1_myBuff = -1; // 超链一我的Buff + db_SC1_BWTBidx = [-1, -1, -1, -1]; // Black White Tower Beam index,四个元素,黑塔-白塔-黑分摊-白分摊 + + db_SC1_round2_drawn = false; // 超链一第二轮是否绘制完毕 + db_SC1_round3_drawn = false; // 超链一第三轮是否绘制完毕 + + mb_Gaia1_dangerPlace = [false, false, false, false, false, false, false, false]; // 小世界一安全角落 + mb_Gaia1_dangerPlace_hasDrawn = false; // 小世界一安全角落是否绘制完毕 + + mb_Classic1_playerGroup = [0, 0, 0, 0, 0, 0, 0, 0]; // 一索尼玩家分组 + mb_Classic1_elements = new List(); // 一索尼元素 + mb_Classic1_etDrawn = false; // 一索尼元素指向是否绘制完毕 + mb_Classic1_implodeDrawn = false; // 一索尼元素毁灭是否绘制完毕 + mb_Classic1_RayDirDrawn = false; // 一索尼射线指路是否绘制完毕 + + mb_Caloric_phase = 0; // 风火分阶段 + mb_Caloric_isFirstTarget = [false, false, false, false, false, false, false, false]; // 一风火初始分摊目标 + mb_Caloric_isWind = [false, false, false, false, false, false, false, false]; // 一风火buff + mb_Caloric_WindPriority = [0, 0, 0, 0, 0, 0, 0, 0]; // 一风火风优先级 + mb_Caloric_FirePriority = [0, 0, 0, 0, 0, 0, 0, 0]; // 一风火火优先级 + mb_Caloric_ParnterStackDirDrawn = false; // 一风火四组分摊指路是否绘制完毕 + mb_Caloric_ParnterStackDrawn = false; // 一风火四组分摊范围是否绘制完毕 + mb_Caloric_SecondParnterStackDirDrawn = false; // 一风火二次分摊指路是否绘制完毕 + mb_Caloric_SecondParnterStackDrawn = false; // 一风火二次分摊范围是否绘制完毕 + mb_Caloric_SecondWindDonutDrawn = false; // 一风火环风是否绘制完毕 + + mb_Exflare_FlarePos = [false, false, false, false]; // 地火核爆区 + mb_Exflare_DirDrawn = false; // 地火指路是否绘制完毕 + + mb_Pangenesis_towerNum = 0; // 黑白塔出塔计数 + mb_Pangenesis_phase = [false, false, false, false, false, false, false, false]; // 黑白塔程序阶段处理是否完成 + mb_Pangenesis_isLong = [false, false, false, false, false, false, false, false]; // 黑白塔,长buff + mb_Pangenesis_isWhite = [false, false, false, false, false, false, false, false]; // 黑白塔,是白Buff + mb_Pangenesis_hasFactor = [false, false, false, false, false, false, false, false]; // 黑白塔,有合成因子 + mb_Pangenesis_isTwo = [false, false, false, false, false, false, false, false]; // 黑白塔,有两层因子 + mb_Pangenesis_shouldWhiteTower = [false, false, false, false, false, false, false, false]; // 黑白塔,应该踩白塔 + mb_Pangenesis_TowerIdxOrder = [-1, -1, -1, -1, -1, -1, -1, -1]; // 黑白塔,初始安排的一塔左,一塔左,二塔上,二塔下,一塔右,一塔右,二塔上,二塔下 + mb_Pangenesis_TowerIdxSecondOrder = [-1, -1, -1, -1, -1, -1, -1, -1]; // 黑白塔,二塔左上,二塔左上,二塔左下,二塔左下,二塔右上,二塔右上,二塔右下,二塔右下 + + + // DebugMsg($"/e Init Success.", accessory); + accessory.Method.MarkClear(); + accessory.Method.RemoveDraw(".*"); + } + + public static void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + var msg = @event["Message"].ToString(); + DebugMsg($"获得玩家发送的消息:{msg}", accessory); + + DebugMsg($"hello", accessory); + + string hasFactorStr = string.Join(", ", mb_Pangenesis_hasFactor); + DebugMsg($"hasFactorStr: {hasFactorStr}", accessory); + + string isLongStr = string.Join(", ", mb_Pangenesis_isLong); + DebugMsg($"isLongStr: {isLongStr}", accessory); + + string isWhiteStr = string.Join(", ", mb_Pangenesis_isWhite); + DebugMsg($"isWhiteStr: {isWhiteStr}", accessory); + + string isTwoStr = string.Join(", ", mb_Pangenesis_isTwo); + DebugMsg($"isTwoStr: {isTwoStr}", accessory); + + string shouldWhiteStr = string.Join(", ", mb_Pangenesis_shouldWhiteTower); + DebugMsg($"shouldWhiteStr: {shouldWhiteStr}", accessory); + + string TowerIdxOrderStr = string.Join(", ", mb_Pangenesis_TowerIdxOrder); + DebugMsg($"TowerIdxOrderStr: {TowerIdxOrderStr}", accessory); + + string TowerIdxSecondOrderStr = string.Join(", ", mb_Pangenesis_TowerIdxSecondOrder); + DebugMsg($"TowerIdxSecondOrderStr: {TowerIdxSecondOrderStr}", accessory); + } + + [ScriptMethod(name: "移除绘图", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=RMV"], userControl: false)] + public void RemoveDraw(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.RemoveDraw(".*"); + } + + #region 门神:左右刀 + + [ScriptMethod(name: "门神:左右刀记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["Param:regex:^(19|20|21|22|23|24)$"], userControl: false)] + public void DB_SideCleaveRecord(Event @event, ScriptAccessory accessory) + { + var param = @event.Param(); + var paramMapping = new Dictionary + { + // { key, (index, value, wing) } + { 19, (0, true, "左上翅膀") }, + { 20, (0, false, "右上翅膀") }, + { 21, (1, true, "左中翅膀") }, + { 22, (1, false, "右中翅膀") }, + { 23, (2, true, "左下翅膀") }, + { 24, (2, false, "右下翅膀") } + }; + if (paramMapping.ContainsKey(param)) + { + var (index, value, wing) = paramMapping[param]; + db_isLeftCleave[index] = value; + DebugMsg($"【门神:左右刀记录】检测到{wing}", accessory); + } + } + + // 左上翅膀 19 82E2 先 33506 + // 右上翅膀 20 82E1 先 33505 + // 左中翅膀 21 + // 右中翅膀 22 + // 左下翅膀 23 82E8 先 33512 + // 右下翅膀 24 82E7 先 33511 + // Trinity of Souls + [ScriptMethod(name: "门神:左右刀绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(33506|33505|33512|33511)$"])] + public void DB_SideCleaveDraw(Event @event, ScriptAccessory accessory) + { + var spos = @event.SourcePosition(); + var srot = @event.SourceRotation(); + var isTopWingFirst = @event.ActionId() == 33506 || @event.ActionId() == 33505; + List sideCleaveLeft = new List(db_isLeftCleave); + + // 如果是上翅膀先亮,顺序不变,否则中翅膀左刀变右刀 + sideCleaveLeft[1] = isTopWingFirst ? sideCleaveLeft[1] : !sideCleaveLeft[1]; + string action1_str = sideCleaveLeft[0] == sideCleaveLeft[1] ? "停" : "穿"; + string action2_str = sideCleaveLeft[1] == sideCleaveLeft[2] ? "停" : "穿"; + // 如果是上翅膀先亮,先执行[0]与[1]的变化,后执行[1]与[2]的变化 + string action_str = isTopWingFirst ? $"先【{action1_str}】后【{action2_str}】" : $"先【{action2_str}】后【{action1_str}】"; + + DebugMsg($"【门神:左右刀绘图】躲避方案为:{action_str}", accessory); + + if (isTopWingFirst) + { + drawSideCleave(sideCleaveLeft[0], 0, 10000, spos, srot, accessory); + drawSideCleave(sideCleaveLeft[1], 10000, 2600, spos, srot, accessory); + drawSideCleave(sideCleaveLeft[2], 12600, 2600, spos, srot, accessory); + } + else + { + drawSideCleave(sideCleaveLeft[0], 12600, 2600, spos, srot, accessory); + drawSideCleave(sideCleaveLeft[1], 10000, 2600, spos, srot, accessory); + drawSideCleave(sideCleaveLeft[2], 0, 10000, spos, srot, accessory); + } + + accessory.Method.TextInfo(action_str, 17000, true); + } + + public static void drawSideCleave(bool isLeft, int delay, int destoryAt, Vector3 spos, float srot, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"左右刀"; + dp.Scale = new(50); + dp.Position = spos; + // dp.Rotation是逆时针,此处代表逆时针转90度 + dp.Rotation = isLeft ? srot + float.Pi / 2 : srot + float.Pi / -2; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + #endregion + + #region 门神:死刑与对话 + + [ScriptMethod(name: "门神:死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33532"])] + public void DB_TankBuster(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"一仇直线死刑-1"; + dp.Scale = new(5, 40); + dp.Owner = @event.SourceId(); + // 读条时已确定目标 + dp.TargetObject = @event.TargetId(); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"一仇直线死刑-2"; + dp.Scale = new(5, 40); + dp.Owner = @event.SourceId(); + // 读条期间可以改变一仇 + dp.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + dp.Color = accessory.Data.DefaultDangerColor; + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "门神:对话绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3353[45])$"])] + public void DB_Dialogos(Event @event, ScriptAccessory accessory) + { + + var sid = @event.SourceId(); + int MyIndex = IndexHelper.getMyIndex(accessory); + + string action_str; + switch (MyIndex) + { + case 0: + action_str = "【目标圈外】远引导"; + break; + case 1: + action_str = "【中间】近引导"; + break; + default: + action_str = "【目标圈间】避开近远"; + break; + } + accessory.Method.TextInfo(action_str, 6200, true); + + var isMT = MyIndex == 0; + var isST = MyIndex == 1; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"对话-近"; + dp.Owner = sid; + dp.Color = isST ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.CentreOrderIndex = 1u; + dp.Delay = 0; + dp.DestoryAt = 5200; + dp.Scale = new(6); + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"对话-远"; + dp.Owner = sid; + dp.Color = isMT ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerFarestOrder; + dp.CentreOrderIndex = 1u; + dp.Delay = 0; + dp.DestoryAt = 6200; + dp.Scale = new(6); + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion + + #region 门神:一范 + + [ScriptMethod(name: "门神:范式阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33517"], userControl: false)] + public void DB_PhaseRecord(Event @event, ScriptAccessory accessory) + { + switch (phase) + { + case P12S_Phase.Init: + phase = P12S_Phase.Paradeigma_I; + break; + case P12S_Phase.Paradeigma_I: + phase = P12S_Phase.Paradeigma_II; + break; + case P12S_Phase.Paradeigma_II: + phase = P12S_Phase.Paradeigma_III; + break; + default: + phase = P12S_Phase.Init; + break; + } + } + + [ScriptMethod(name: "门神:一范天使位置记录", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:16172"], userControl: false)] + public void DB_Paradeigma_I_PositionRecord(Event @event, ScriptAccessory accessory) + { + lock (db_PD1_lockObject) + { + // 只检验一次,如果 Z < 100 则在北 + if (phase != P12S_Phase.Paradeigma_I || db_PD1_isChecked) return; + var spos = @event.SourcePosition(); + db_PD1_isNorthFirst = spos.Z < 100; + db_PD1_isChecked = true; + DebugMsg($"【门神:一范天使位置记录】一范天使在 {(db_PD1_isNorthFirst ? "北" : "南")}", accessory); + } + } + + [ScriptMethod(name: "门神:一范天使站位点绘图", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:16172"])] + public void DB_Paradeigma_I_Waymark(Event @event, ScriptAccessory accessory) + { + Task.Delay(100).ContinueWith(t => + { + lock (db_PD1_lockObject) + { + if (db_PD1_drawn) return; + if (!db_PD1_isChecked) return; + + if (PD1Strategy == PD1StrategyEnum.正攻_Regular) + { + int MyIndex = IndexHelper.getMyIndex(accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + // 偶数Idx为第一轮引导(MTH1D1D3) + bool isFirstRound = MyIndex % 2 == 0; + int MyPos = MyIndex / 2; + + if (isFirstRound) + { + drawPD1Spread(db_PD1_isNorthFirst, MyPos, 1000, 10000, accessory); + drawPD1Safe(db_PD1_isNorthFirst, 11000, 5000, accessory); + } + else + { + drawPD1Safe(db_PD1_isNorthFirst, 1000, 10000, accessory); + drawPD1Spread(db_PD1_isNorthFirst, MyPos, 11000, 5000, accessory); + } + } + + else if (PD1Strategy == PD1StrategyEnum.无敌_Invuln) + { + int MyIndex = IndexHelper.getMyIndex(accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + + switch (MyIndex) + { + case 0: + case 1: + // 直接靠近开无敌 + drawPD1Spread(db_PD1_isNorthFirst, 0, 1000, 15000, accessory); + break; + default: + // 直接人群 + drawPD1Safe(db_PD1_isNorthFirst, 1000, 15000, accessory); + break; + } + } + + else if (PD1Strategy == PD1StrategyEnum.无敌改_InvulnEx) + { + int MyIndex = IndexHelper.getMyIndex(accessory); + var dp = accessory.Data.GetDefaultDrawProperties(); + + switch (MyIndex) + { + case 0: + // MT 先远离后靠近开无敌 + drawPD1Spread(db_PD1_isNorthFirst, 2, 1000, 10000, accessory); + drawPD1Spread(db_PD1_isNorthFirst, 0, 11000, 5000, accessory); + break; + case 1: + // ST 直接靠近开无敌 + drawPD1Spread(db_PD1_isNorthFirst, 0, 1000, 15000, accessory); + break; + case 6: + // D3 先靠近后回人群 + drawPD1Spread(db_PD1_isNorthFirst, 1, 1000, 10000, accessory); + drawPD1Safe(db_PD1_isNorthFirst, 11000, 5000, accessory); + break; + default: + drawPD1Safe(db_PD1_isNorthFirst, 1000, 15000, accessory); + break; + } + } + + db_PD1_drawn = true; + } + }); + } + + public static void drawPD1Spread(bool isNorthSpread, int MyPos, int delay, int destoryAt, ScriptAccessory accessory) + { + Vector3[,] pos = new Vector3[2, 5]; + pos[0, 0] = new(100, 0, 95); // 一范引导位:北2 / MTST + pos[0, 1] = new(100, 0, 100); // 一范引导位:北1 / H1H2 + pos[0, 2] = new(100, 0, 90); // 一范引导位:北3 / D1D2 + pos[0, 3] = new(100, 0, 85); // 一范引导位:北4 / D3D4 + pos[0, 4] = new(100, 0, 109); // 一范集合位:南 + + pos[1, 0] = new(100, 0, 105); // 一范引导位:南2 / MTST + pos[1, 1] = new(100, 0, 100); // 一范引导位:南1 / H1H2 + pos[1, 2] = new(100, 0, 110); // 一范引导位:南3 / D1D2 + pos[1, 3] = new(100, 0, 115); // 一范引导位:南4 / D3D4 + pos[1, 4] = new(100, 0, 91); // 一范集合位:北 + + var dp = accessory.Data.GetDefaultDrawProperties(); + for (int i = 0; i < 4; i++) + { + dp.Name = $"一范引导站位-{i}"; + dp.Scale = new(1.5f); + // 如果北引导,画4北 + dp.Position = pos[isNorthSpread ? 0 : 1, i]; + dp.Color = (i == MyPos) ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"一范分散站位指路"; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = pos[isNorthSpread ? 0 : 1, MyPos]; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Scale = new(1f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public static void drawPD1Safe(bool isNorthSpread, int delay, int destoryAt, ScriptAccessory accessory) + { + Vector3[,] pos = new Vector3[2, 5]; + pos[0, 0] = new(100, 0, 95); // 一范引导位:北2 / MTST + pos[0, 1] = new(100, 0, 100); // 一范引导位:北1 / H1H2 + pos[0, 2] = new(100, 0, 90); // 一范引导位:北3 / D1D2 + pos[0, 3] = new(100, 0, 85); // 一范引导位:北4 / D3D4 + pos[0, 4] = new(100, 0, 109); // 一范集合位:南 + + pos[1, 0] = new(100, 0, 105); // 一范引导位:南2 / MTST + pos[1, 1] = new(100, 0, 100); // 一范引导位:南1 / H1H2 + pos[1, 2] = new(100, 0, 110); // 一范引导位:南3 / D1D2 + pos[1, 3] = new(100, 0, 115); // 一范引导位:南4 / D3D4 + pos[1, 4] = new(100, 0, 91); // 一范集合位:北 + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"一范安全站位"; + dp.Scale = new(1.5f); + // 如果北引导,安全于南 + dp.Position = pos[isNorthSpread ? 0 : 1, 4]; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"一范安全站位指路"; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = pos[isNorthSpread ? 0 : 1, 4]; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Scale = new(1f); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + #endregion + #region 门神:二范 + + [ScriptMethod(name: "门神:二范小怪连线记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3352[12])$"], userControl: false)] + public void DB_Paradeigma_II_LineRecord(Event @event, ScriptAccessory accessory) + { + // 如果是连黑线(33522),则需要白塔 + bool shouldWhiteTower = @event.ActionId() == 33522; + Vector3 spos = @event.SourcePosition(); + string log = ""; + + // 先获得北侧天使 + // 北0,东1,南2,西3 + if (spos.Z < 80) + { + db_PD2_shouldWhiteTower[0] = shouldWhiteTower; + log += $"北天使{(shouldWhiteTower ? "黑" : "白")}线"; + + if (spos.X > 100) + db_PD2_fromRightBottom = true; // 如果北侧天使偏右,放塔判断从右下开始 + + log += $"{(db_PD2_fromRightBottom ? "偏右" : "偏左")}。"; + } + else + { + int index = (spos.X > 120) ? 1 : (spos.Z > 120) ? 2 : 3; + db_PD2_shouldWhiteTower[index] = shouldWhiteTower; + log += $"{(index == 1 ? "东" : index == 2 ? "南" : "西")}天使{(shouldWhiteTower ? "黑" : "白")}线。"; + } + + DebugMsg($"【门神:二范小怪连线记录】{log}", accessory); + } + + [ScriptMethod(name: "门神:二范黑白塔标记记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3579|3580)$"], userControl: false)] + public void DB_Paradeigma_II_TowerRecord(Event @event, ScriptAccessory accessory) + { + lock (db_PD2_lockObject) + { + var tid = @event.TargetId(); + var targetIndex = IndexHelper.getPlayerIdIndex(tid, accessory); + if (targetIndex == -1) return; + + // 被选中代表要放塔,3579 灵临刻印,放白塔 + db_PD2_isChosenTower[targetIndex] = true; + db_PD2_isWhiteTower[targetIndex] = @event.StatusID() == 3579; + db_PD2_towerRecordNum++; + } + } + + [ScriptMethod(name: "门神:小怪连线绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3352[12])$"])] + public void DB_Paradeigma_II_LineDraw(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var sid = @event.SourceId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"小怪连线"; + dp.Scale = new(5); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Owner = sid; + dp.TargetObject = tid; + dp.Color = tid == accessory.Data.Me ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 8700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "门神:小怪冲击波绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33518"])] + public void DB_Paradeigma_II_ShootDraw(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"小怪连线"; + dp.Scale = new(10, 60); + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "门神:二范黑白塔绘图", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3579|3580)$"])] + public void DB_Paradeigma_II_TowerDraw(Event @event, ScriptAccessory accessory) + { + Task.Delay(100).ContinueWith(t => + { + lock (db_PD2_lockObject) + { + if (db_PD2_drawn) return; + + if (db_PD2_towerRecordNum != 4) return; + db_PD2_drawn = true; + + int MyIndex = IndexHelper.getMyIndex(accessory); + if (MyIndex == -1 || !db_PD2_isChosenTower[MyIndex]) return; + + var tposIndex = MyIndex < 4 + ? db_PD2_shouldWhiteTower.IndexOf(db_PD2_isWhiteTower[MyIndex]) + : db_PD2_shouldWhiteTower.LastIndexOf(db_PD2_isWhiteTower[MyIndex]); + + DebugMsg($"【门神:二范黑白塔绘图】需将塔引导给{tposIndex}号天使", accessory); + + if (!db_PD2_fromRightBottom) tposIndex++; + + Vector3[] tpos = new Vector3[5]; + tpos[0] = new(110, 0, 110); + tpos[1] = new(90, 0, 110); + tpos[2] = new(90, 0, 90); + tpos[3] = new(110, 0, 90); + tpos[4] = new(110, 0, 110); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"黑白塔放置位"; + dp.Scale = new(1); + dp.Color = accessory.Data.DefaultSafeColor.WithW(3f); + dp.Delay = 0; + dp.DestoryAt = 11000; + dp.Position = tpos[tposIndex]; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Scale = new(1f); + dp.Name = $"黑白塔放置位指路"; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = tpos[tposIndex]; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 11000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + }); + } + #endregion + + + + + #region 门神:超链I(第一步,钢月分摊分散) + + [ScriptMethod(name: "门神:进入超链阶段I", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33498"], userControl: false)] + public void DB_SuperChain_I_PhaseRecord(Event @event, ScriptAccessory accessory) + { + phase = P12S_Phase.SuperChain_I; + } + + [ScriptMethod(name: "门神:超链I元素收集", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(16176|16177|16178|16179|16180)$"], userControl: false)] + public void DB_SuperChain_I_TheoryCollect(Event @event, ScriptAccessory accessory) + { + lock (db_SC1_lockObject) + { + if (phase != P12S_Phase.SuperChain_I) return; + db_SC1_theories.Add(@event.SourceId()); + DebugMsg($"捕捉到新的超链元素,当前列表内有{db_SC1_theories.Count()}个", accessory); + } + } + + [ScriptMethod(name: "门神:超链I第一组绘图", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(16176|16177|16178|16179|16180)$"])] + public void DB_SuperChain_I_FirstRound(Event @event, ScriptAccessory accessory) + { + Task.Delay(100).ContinueWith(t => + { + lock (db_SC1_lockObject) + { + if (db_SC1_round1_drawn) return; + if (phase != P12S_Phase.SuperChain_I) return; + if (db_SC1_theories.Count() != 3) return; + db_SC1_round1_drawn = true; + DebugMsg($"进入超链I第一组绘图。", accessory); + + IGameObject? destTheory = null; // 目标点 超链元素 + + for (int i = 0; i < 3; i++) + { + var theoryObject = accessory.GetById(db_SC1_theories[i]); + if (theoryObject == null) return; + switch (theoryObject.DataId) + { + case 16176: + destTheory = theoryObject; + break; + case 16177: + db_SC1_isOut = true; + break; + case 16178: + db_SC1_isOut = false; + break; + case 16179: + db_SC1_isSpread = true; + break; + case 16180: + db_SC1_isSpread = false; + break; + } + } + if (destTheory == null) return; + + // 画钢铁月环范围 + drawCircleDonutAtPos(destTheory.Position, 0, 11000, db_SC1_isOut, accessory); + // 画分摊分散扇形范围 + drawSpreadStackAtPos(destTheory.Position, 7000, 4000, db_SC1_isSpread, accessory); + // 画分摊分散站位 + drawSpreadStackStdPos(destTheory.Position, 0, 11500, db_SC1_isOut, db_SC1_isSpread, accessory); + } + }); + } + + private static void drawCircleDonutAtPos(Vector3 pos, int delay, int destoryAt, bool isCircle, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = "超链元素"; + dp.Position = pos; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Color = accessory.Data.DefaultDangerColor; + if (isCircle) + { + dp.Scale = new(7); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + else + { + dp.Scale = new(30); + dp.InnerScale = new(6); + dp.Radian = float.Pi * 2; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + } + + private static void drawSpreadStackAtPos(Vector3 pos, int delay, int destoryAt, bool isSpread, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + var MyIndex = IndexHelper.getMyIndex(accessory); + for (int i = 0; i < (isSpread ? 8 : 4); i++) + { + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"四分八分{i}"; + dp.Position = pos; + dp.TargetObject = accessory.Data.PartyList[i]; + dp.Color = (i == MyIndex || i == MyIndex - 4) ? accessory.Data.DefaultSafeColor.WithW(0.5f) : accessory.Data.DefaultDangerColor.WithW(0.5f); + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Scale = new Vector2(40); + dp.Radian = isSpread ? float.Pi / 180 * 30 : float.Pi / 180 * 35; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + + private static void drawSpreadStackStdPos(Vector3 pos, int delay, int destoryAt, bool isCircle, bool isSpread, ScriptAccessory accessory) + { + int MyIndex = IndexHelper.getMyIndex(accessory); + float deg_init = DirectionCalc.FindRadian(pos, new Vector3(100, 0, 100)); + float deg; + List safePoint = isSpread ? [6, 1, 5, 2, 7, 0, 4, 3] : [3, 0, 2, 1, 3, 0, 2, 1]; + var dp = accessory.Data.GetDefaultDrawProperties(); + for (int i = 0; i < (isSpread ? 8 : 4); i++) + { + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"定位{i}"; + dp.Scale = new(1f); + deg = isSpread ? deg_init + float.Pi / 4 * i + float.Pi / 8 : deg_init + float.Pi / 2 * i + float.Pi / 4; + dp.Position = DirectionCalc.ExtendPoint(pos, deg, isCircle ? 8 : 5); + dp.Color = safePoint[MyIndex] == i ? posColorPlayer.V4.WithW(3f) : posColorNormal.V4; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + #endregion + + #region 门神:超链I(第二步,黑白左右分摊) + + [ScriptMethod(name: "门神:超链IBuff收集", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3576|3577|3579|3580|3581|3582)$"], userControl: false)] + public void DB_SuperChain_I_BuffRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.SuperChain_I) return; + if (db_SC1_myBuff != -1 && !db_SC1_BWTBidx.Contains(-1)) return; + var tid = @event.TargetId(); + var sid = @event.StatusID(); + var sidMapping = new Dictionary + { + { 3581, (0, "白分摊", 3) }, + { 3582, (1, "黑分摊", 2) }, + { 3579, (2, "白塔", 1) }, + { 3580, (3, "黑塔", 0) }, + // { 3578, (4, "分散", -1) }, + { 3576, (5, "初始白", -1) }, + { 3577, (6, "初始黑", -1) } + }; + if (sidMapping.ContainsKey(sid)) + { + var (value, mention, lidx) = sidMapping[sid]; + if (lidx != -1) + db_SC1_BWTBidx[lidx] = IndexHelper.getPlayerIdIndex(tid, accessory); + if (tid == accessory.Data.Me) + { + db_SC1_myBuff = value; + DebugMsg($"捕捉到自身BUFF为{mention}", accessory); + } + } + } + + [ScriptMethod(name: "门神:超链I第二组绘图", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(16176|16177|16178|16179|16180)$"])] + public void DB_SuperChain_I_SecondRound(Event @event, ScriptAccessory accessory) + { + IGameObject? destDonutChar = null; + Task.Delay(100).ContinueWith(t => + { + lock (db_SC1_lockObject) + { + if (db_SC1_theories.Count() != 7) return; + if (phase != P12S_Phase.SuperChain_I) return; + if (db_SC1_round2_drawn) return; + db_SC1_round2_drawn = true; + DebugMsg($"进入超链I第二组绘图。", accessory); + + // 取出后4个元素,两个终点,一钢铁一月环 + List SC1_SubList = db_SC1_theories.GetRange(3, 4); + + IGameObject? dest1Theory = null; + IGameObject? dest2Theory = null; + IGameObject? inTheory = null; + IGameObject? outTheory = null; + + // 判断终点位置 + for (int i = 0; i < 4; i++) + { + var theoryObject = accessory.GetById(SC1_SubList[i]); + if (theoryObject == null) return; + + switch (theoryObject.DataId) + { + case 16176: + if (dest1Theory == null) + dest1Theory = theoryObject; + else + dest2Theory = theoryObject; + break; + case 16177: + outTheory = theoryObject; + break; + case 16178: + inTheory = theoryObject; + break; + } + } + destDonutChar = GetDonutChar(dest1Theory, dest2Theory, inTheory, outTheory); + if (destDonutChar == null) return; + drawCircleDonutAtPos(destDonutChar.Position, 6500, 7000, false, accessory); + } + }); + + Task.Delay(3500).ContinueWith(t => + { + bool atLeft = db_SC1_myBuff == 2 || db_SC1_myBuff == 5 || db_SC1_myBuff == 1; + if (destDonutChar == null) return; + drawStackStdPos(destDonutChar.Position, 3000, 7000, atLeft, accessory); + }); + } + + private static IGameObject? GetDonutChar(IGameObject? dest1Theory, IGameObject? dest2Theory, IGameObject? inTheory, IGameObject? outTheory) + { + if (dest1Theory == null || dest2Theory == null || inTheory == null || outTheory == null) return null; + float dest1ToDonut = new Vector2(dest1Theory.Position.X - inTheory.Position.X, + dest1Theory.Position.Z - inTheory.Position.Z).Length(); + float dest2ToDonut = new Vector2(dest2Theory.Position.X - inTheory.Position.X, + dest2Theory.Position.Z - inTheory.Position.Z).Length(); + float dest1ToCircle = new Vector2(dest1Theory.Position.X - outTheory.Position.X, + dest1Theory.Position.Z - outTheory.Position.Z).Length(); + float dest2ToCircle = new Vector2(dest2Theory.Position.X - outTheory.Position.X, + dest2Theory.Position.Z - outTheory.Position.Z).Length(); + + if (dest1ToDonut < dest1ToCircle && dest2ToDonut > dest2ToCircle) + return dest1Theory; + if (dest1ToDonut > dest1ToCircle && dest2ToDonut < dest2ToCircle) + return dest2Theory; + + return null; + } + + private static void drawStackStdPos(Vector3 pos, int delay, int destoryAt, bool atLeft, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"左右分摊{atLeft}"; + dp.TargetPosition = DirectionCalc.RotatePoint(pos, new Vector3(100, 0, 100), atLeft ? float.Pi / 180 * 17 : float.Pi / 180 * -17); + dp.Position = new(100, 0, 100); + dp.Color = posColorPlayer.V4; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + dp.Scale = new(5, 20); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + #endregion + + #region 门神:超链I(第三步,钢月、放塔、分散) + [ScriptMethod(name: "门神:超链I第三组绘图", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(16176|16177|16178|16179|16180)$"])] + public void DB_SuperChain_I_ThirdRound(Event @event, ScriptAccessory accessory) + { + Task.Delay(100).ContinueWith(t => + { + lock (db_SC1_lockObject) + { + if (db_SC1_theories.Count() != 10) return; + if (phase != P12S_Phase.SuperChain_I) return; + // 刷出一个终点、钢铁月环 + if (db_SC1_round3_drawn) return; + db_SC1_round3_drawn = true; + + if (DebugMode) + accessory.Method.SendChat($"/e [DEBUG]:进入超链I第三组绘图……"); + + List SC1_SubList = db_SC1_theories.GetRange(7, 3); + + // 判断终点位置 + IGameObject? destTheory = null; + IGameObject? inTheory = null; + IGameObject? outTheory = null; + + for (int i = 0; i < 3; i++) + { + var theoryObject = accessory.GetById(SC1_SubList[i]); + if (theoryObject == null) return; + + switch (theoryObject.DataId) + { + case 16176: + destTheory = theoryObject; + break; + case 16177: + outTheory = theoryObject; + break; + case 16178: + inTheory = theoryObject; + break; + } + } + + if (destTheory == null || inTheory == null || outTheory == null) return; + + float destToDonut = new Vector2(destTheory.Position.X - inTheory.Position.X, + destTheory.Position.Z - inTheory.Position.Z).Length(); + float destToCircle = new Vector2(destTheory.Position.X - outTheory.Position.X, + destTheory.Position.Z - outTheory.Position.Z).Length(); + + bool isDonutFirst = destToDonut < destToCircle; + + // 画钢铁月环范围 + drawCircleDonutAtPos(destTheory.Position, 10000, 4800, !isDonutFirst, accessory); + drawCircleDonutAtPos(destTheory.Position, 14800, 2000, isDonutFirst, accessory); + + switch (db_SC1_myBuff) + { + case 0: + // 白分摊,踩黑右塔 + // 此处画图逻辑,找到放塔人,先danger,后safe + drawTowerCircleOnPlayer(db_SC1_BWTBidx[0], false, accessory); + break; + case 1: + // 黑分摊,踩白左塔 + drawTowerCircleOnPlayer(db_SC1_BWTBidx[1], false, accessory); + break; + case 2: + // 白塔,左侧放塔 + drawTowerCircleOnPlayer(db_SC1_BWTBidx[1], true, accessory); + drawSC1TowerDir(destTheory.Position, 2, accessory); + break; + case 3: + // 黑塔,右侧放塔 + drawTowerCircleOnPlayer(db_SC1_BWTBidx[0], true, accessory); + drawSC1TowerDir(destTheory.Position, 3, accessory); + break; + case 5: + case 6: + // 初始白黑,分散 + drawSC1SpreadDir(destTheory.Position, accessory); + drawSC1SpreadCircle(accessory); + break; + default: + break; + } + } + }); + } + private static void drawTowerCircleOnPlayer(int playerIdx, bool isSafe, ScriptAccessory accessory) + { + var dp = AssignDp.drawCircle(accessory.Data.PartyList[playerIdx], 14300, 3000, $"放塔跟随{playerIdx}", accessory); + dp.Scale = new(3); + dp.Color = isSafe ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private static void drawSC1SpreadDir(Vector3 dest, ScriptAccessory accessory) + { + var angle_toBoss = DirectionCalc.rad2Angle(DirectionCalc.FindRadian(dest, new(100, 0, 100))); + List angle_spread = new List { + DirectionCalc.angle2Rad(angle_toBoss - 20), + DirectionCalc.angle2Rad(angle_toBoss + 20), + DirectionCalc.angle2Rad(angle_toBoss - 160), + DirectionCalc.angle2Rad(angle_toBoss + 160), + }; + var myIndex = IndexHelper.getMyIndex(accessory); + + for (int i = 0; i < 4; i++) + { + DebugMsg($"{myIndex} vs {i}", accessory); + var tpos = DirectionCalc.ExtendPoint(dest, angle_spread[i], 15); + var dp = AssignDp.dirPos2Pos(dest, tpos, 14800, 5000, $"分散{i}", accessory); + dp.Color = ((myIndex == i) || (myIndex == i + 4)) ? posColorPlayer.V4.WithW(3f) : posColorNormal.V4; + dp.Scale = new(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + } + + private static void drawSC1TowerDir(Vector3 dest, int myBuff, ScriptAccessory accessory) + { + // myBuff 2 白塔 左 + // myBuff 3 黑塔 右 + var angle_toBoss = DirectionCalc.rad2Angle(DirectionCalc.FindRadian(dest, new(100, 0, 100))); + + List angle_tower = new List { + DirectionCalc.angle2Rad(angle_toBoss - 52), + DirectionCalc.angle2Rad(angle_toBoss + 52), + }; + + var tpos = DirectionCalc.ExtendPoint(dest, angle_tower[myBuff - 2], 12); + var dp = AssignDp.dirPos2Pos(dest, tpos, 14300, 5000, $"放塔{myBuff}指路", accessory); + dp.Scale = new(2f); + dp.Color = posColorPlayer.V4.WithW(3f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + private static void drawSC1SpreadCircle(ScriptAccessory accessory) + { + // 不在db_SC1_BWTBidx里的就是分散的 + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + if (db_SC1_BWTBidx.Contains(i)) continue; + var dp = AssignDp.drawCircle(accessory.Data.PartyList[i], 15800, 4000, $"分散{i}", accessory); + dp.Scale = new(6); + dp.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "门神:超链I踩塔提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(33549|33548)$"])] + public void DB_SuperChain_I_TowerDir(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.SuperChain_I) return; + // 灵极白塔 33548 + // 星极黑塔 33549 + var tpos = @event.TargetPosition(); + var aid = @event.ActionId(); + + // 我是白分摊,检测到黑塔 || 我是黑分摊,检测到白塔 + var match = (db_SC1_myBuff == 0 && aid == 33549) || (db_SC1_myBuff == 1 && aid == 33548); + + var dp = AssignDp.drawStatic(tpos, 0, 0, 3000, $"踩塔{aid}", accessory); + dp.Color = match ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.Scale = new(3f); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + if (match) + { + var dp0 = AssignDp.dirPos(tpos, 0, 3000, $"踩塔{aid}指路", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + } + #endregion + + #region 本体:小世界一 + + [ScriptMethod(name: "本体:阶段转换小世界一(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33574"], userControl: false)] + public void MB_PhaseChange_Gaia(Event @event, ScriptAccessory accessory) + { + phase = phase switch + { + P12S_Phase.Classic_I => P12S_Phase.Gaia_II, + P12S_Phase.Caloric_I => P12S_Phase.Gaia_II, + P12S_Phase.Exflare => P12S_Phase.Gaia_II, + P12S_Phase.Pangenesis => P12S_Phase.Gaia_II, + P12S_Phase.Classic_II => P12S_Phase.Gaia_II, + P12S_Phase.Caloric_II => P12S_Phase.Gaia_II, + _ => P12S_Phase.Gaia_I, + }; + DebugMsg($"当前阶段为:{phase}", accessory); + } + + [ScriptMethod(name: "本体:小世界,小怪激光", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33584"])] + public void MB_Gaia_BeamRay(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Gaia_I && phase != P12S_Phase.Gaia_II) return; + var sid = @event.SourceId(); + var sname = @event.SourceName(); + + var dp = assignDp_Line(sid, $"小怪激光{sid}", 6, 20, 0, 7000, accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + DebugMsg($"捕捉到[{sid}|{sname}]释放激光并绘图。", accessory); + } + + private static DrawPropertiesEdit assignDp_Line(uint sid, string name, int width, int length, int delay, int destory, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(width, length); // 宽度为6,长度为20 + dp.Owner = sid; // 从哪个单位前方绘图 + dp.Color = accessory.Data.DefaultDangerColor; // 绘图颜色 + dp.Delay = delay; // 从捕获到对应日志行后,延迟多少毫秒开始绘图 + dp.DestoryAt = destory; // 从绘图出现后,经过多少毫秒绘图消失 + return dp; + } + + [ScriptMethod(name: "本体:小世界,安全区", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3357[789])$"])] + public void MB_Gaia1_SafetyField(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Gaia_I && phase != P12S_Phase.Gaia_II) return; + + // 三种地心说安全形式 + const uint VERTICAL = 33577; + const uint DONUT = 33578; + const uint HORIZON = 33579; + + var aid = @event.ActionId(); + var myIndex = IndexHelper.getMyIndex(accessory); + + switch (aid) + { + case VERTICAL: + DebugMsg($"捕捉到地心说垂直安全[{aid}]。", accessory); + drawVerticalSafetyField(accessory); + drawVerticalSpreadPos(myIndex, accessory); + break; + case DONUT: + DebugMsg($"捕捉到地心说环形安全[{aid}]。", accessory); + drawDonutSafetyField(accessory); + drawDonutSpreadPos(myIndex, accessory); + break; + case HORIZON: + DebugMsg($"捕捉到地心说水平安全[{aid}]。", accessory); + drawHorizonSafetyField(accessory); + drawHorizonSpreadPos(myIndex, accessory); + break; + default: + break; + } + } + + private static DrawPropertiesEdit assignDp_DonutSafetyField(int scale, int inner_scale, int delay, int destory, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Position = new(100, 0, 100); + dp.Radian = float.Pi * 2; + dp.Scale = new(scale); + dp.InnerScale = new(inner_scale); + dp.Color = accessory.Data.DefaultDangerColor.WithW(4f); + dp.Delay = delay; + dp.DestoryAt = destory; + return dp; + } + + private static void drawDonutSafetyField(ScriptAccessory accessory) + { + var dp = assignDp_DonutSafetyField(7, 3, 0, 7000, "地心说月环外", accessory); + dp.Position = new(100, 0, 90); + dp.Color = ColorHelper.colorDark.V4.WithW(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + + var dp2 = AssignDp.drawStatic(new(100, 0, 90), 0, 0, 7000, "地心说月环内", accessory); + dp2.Color = ColorHelper.colorDark.V4.WithW(4f); + dp2.Scale = new(2); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + } + + private static void drawVerticalSafetyField(ScriptAccessory accessory) + { + var dp = AssignDp.drawStatic(new(100, 0, 82), float.Pi, 0, 7000, "地心说中线", accessory); + dp.Scale = new(4, 20); + dp.Color = ColorHelper.colorDark.V4.WithW(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + var dp2 = AssignDp.drawStatic(new(95, 0, 82), float.Pi, 0, 7000, "地心说左线", accessory); + dp2.Scale = new(4, 20); + dp2.Color = ColorHelper.colorDark.V4.WithW(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp2); + + var dp3 = AssignDp.drawStatic(new(105, 0, 82), float.Pi, 0, 7000, "地心说右线", accessory); + dp3.Scale = new(4, 20); + dp3.Color = ColorHelper.colorDark.V4.WithW(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp3); + } + + private static void drawHorizonSafetyField(ScriptAccessory accessory) + { + var dp = AssignDp.drawStatic(new(92, 0, 90), float.Pi / 2, 0, 7000, "地心说中线", accessory); + dp.Scale = new(4, 20); + dp.Color = ColorHelper.colorDark.V4.WithW(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + var dp2 = AssignDp.drawStatic(new(95, 0, 82), float.Pi / 2, 0, 7000, "地心说上线", accessory); + dp2.Scale = new(4, 20); + dp2.Color = ColorHelper.colorDark.V4.WithW(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp2); + + var dp3 = AssignDp.drawStatic(new(105, 0, 82), float.Pi / 2, 0, 7000, "地心说右线", accessory); + dp3.Scale = new(4, 20); + dp3.Color = ColorHelper.colorDark.V4.WithW(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp3); + } + + [ScriptMethod(name: "本体:小世界,分散", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0016"])] + public void MB_SpreadIcon(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Gaia_I && phase != P12S_Phase.Gaia_II) return; + var tid = @event.TargetId(); + var uid = accessory.Data.Me; + + var dp = AssignDp.drawCircle(tid, 0, 3000, $"神罚{tid}", accessory); + dp.Color = tid == uid ? accessory.Data.DefaultSafeColor.WithW(3f) : accessory.Data.DefaultDangerColor.WithW(1.5f); + dp.Scale = new(1); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private static void drawDonutSpreadPos(int myIndex, ScriptAccessory accessory) + { + List spreadDir = [-0.5f, 0.5f, -1.5f, 1.5f, -3.5f, 3.5f, -2.5f, 2.5f]; + Vector3[] safePos = new Vector3[8]; + for (int i = 0; i < 8; i++) + { + safePos[i] = DirectionCalc.ExtendPoint(new(100, 0, 90), DirectionCalc.angle2Rad(spreadDir[i] * 45), 2.5f); + } + + for (int i = 0; i < 8; i++) + { + var dp = AssignDp.drawStatic(safePos[i], 0, 0, 7000, $"月环分散位置{i}", accessory); + dp.Scale = new(0.5f); + dp.Color = myIndex == i ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (myIndex != i) continue; + var dp0 = AssignDp.dirPos(safePos[i], 0, 7000, $"月环分散位置指路{i}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + } + + private static void drawVerticalSpreadPos(int myIndex, ScriptAccessory accessory) + { + List spreadDirLR = [-1, 1, -1, 1, -1, 1, -1, 1]; + List spreadDirUD = [-3, -3, -1, -1, 3, 3, 1, 1]; + + Vector3[] safePos = new Vector3[8]; + for (int i = 0; i < 8; i++) + { + safePos[i] = new Vector3(100, 0, 90) + new Vector3(spreadDirLR[i] * 2.5f, 0, 0) + new Vector3(0, 0, spreadDirUD[i] * 2); + } + + for (int i = 0; i < 8; i++) + { + var dp = AssignDp.drawStatic(safePos[i], 0, 0, 7000, $"竖直分散位置{i}", accessory); + dp.Scale = new(0.5f); + dp.Color = myIndex == i ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (myIndex != i) continue; + var dp0 = AssignDp.dirPos(safePos[i], 0, 7000, $"竖直分散位置指路{i}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + } + + private static void drawHorizonSpreadPos(int myIndex, ScriptAccessory accessory) + { + List spreadDirUD = [-1, -1, -1, -1, 1, 1, 1, 1]; + List spreadDirLR = [-1, 1, -3, 3, -1, 1, -3, 3]; + + // 84 88 92 96 + + Vector3[] safePos = new Vector3[8]; + for (int i = 0; i < 8; i++) + { + safePos[i] = new Vector3(100, 0, 90) + new Vector3(spreadDirLR[i] * 2, 0, 0) + new Vector3(0, 0, spreadDirUD[i] * 2.5f); + } + + for (int i = 0; i < 8; i++) + { + var dp = AssignDp.drawStatic(safePos[i], 0, 0, 7000, $"水平分散位置{i}", accessory); + dp.Scale = new(0.5f); + dp.Color = myIndex == i ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (myIndex != i) continue; + var dp0 = AssignDp.dirPos(safePos[i], 0, 7000, $"水平分散位置指路{i}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + } + + [ScriptMethod(name: "本体:小世界,分散删除(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:33582", "TargetIndex:1"], userControl: false)] + public void MB_Gaia1_SpreadIconRemove(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Gaia_I && phase != P12S_Phase.Gaia_II) return; + var tid = @event.TargetId(); + accessory.Method.RemoveDraw($"神罚{tid}"); + } + + [ScriptMethod(name: "本体:小世界一,角落安全区", eventType: EventTypeEnum.PlayActionTimeline, eventCondition: ["Id:4562", "SourceDataId:16182"])] + public async void MB_Gaia1_PartySafeCorner(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Gaia_I) return; + lock (mb_Gaia1_dangerPlace) + { + var spos = @event.SourcePosition(); + var dangerIdx = DirectionCalc.PositionRoundToDirs(spos, new(100, 0, 90), 8); + mb_Gaia1_dangerPlace[dangerIdx] = true; + mb_Gaia1_dangerPlace[dangerIdx >= 4 ? dangerIdx - 4 : dangerIdx + 4] = true; + DebugMsg($"方位{dangerIdx}与{(dangerIdx >= 4 ? dangerIdx - 4 : dangerIdx + 4)}被设置为危险", accessory); + } + + await Task.Delay(100); + + lock (mb_Gaia1_dangerPlace) + { + if (mb_Gaia1_dangerPlace.Count(x => x) != 6) return; // 若危险方位不为6个,不执行后续代码 + if (mb_Gaia1_dangerPlace_hasDrawn) return; + + var myIndex = IndexHelper.getMyIndex(accessory); + for (int i = 0; i < mb_Gaia1_dangerPlace.Count(); i++) + { + if (mb_Gaia1_dangerPlace[i]) continue; + var isMySafeCorner = drawSafeCorner_Gaia1(i, myIndex, accessory); + DebugMsg($"绘制出安全角落{i},{(isMySafeCorner ? "且为指路目的地" : "不指路")}", accessory); + } + mb_Gaia1_dangerPlace_hasDrawn = true; + } + } + private static bool drawSafeCorner_Gaia1(int posIdx, int myIndex, ScriptAccessory accessory) + { + var dp = assignDp_SafeCornerCircle(posIdx, $"安全角落{posIdx}", accessory); + // DPS安全区为右下半场,对应方位为2、3、4、5。 + var isMySafeCorner = ((myIndex >= 4) && (posIdx >= 2) && (posIdx <= 5)) || ((myIndex < 4) && ((posIdx <= 1) || (posIdx >= 6))); + + dp.Color = isMySafeCorner ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + if (!isMySafeCorner) return false; + var pos = DirectionCalc.ExtendPoint(new(100, 0, 90), DirectionCalc.angle2Rad(45 * posIdx), 6.5f); + var dp0 = AssignDp.dirPos(pos, 0, 6500, $"指路{posIdx}预备", accessory); + dp0.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + + var dp1 = AssignDp.dirPos(pos, 6500, 3500, $"指路{posIdx}", accessory); + dp1.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + + return true; + } + + private static DrawPropertiesEdit assignDp_SafeCornerCircle(int idx, string name, ScriptAccessory accessory) + { + var pos = DirectionCalc.ExtendPoint(new(100, 0, 90), DirectionCalc.angle2Rad(45 * idx), 6.5f); + var dp = AssignDp.drawStatic(pos, 0, 0, 10000, name, accessory); + dp.Scale = new(1); + return dp; + } + + #endregion + + + #region 本体:一索尼 + + [ScriptMethod(name: "本体:阶段转换索尼(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33585"], userControl: false)] + public void MB_PhaseChange_Classic(Event @event, ScriptAccessory accessory) + { + phase = phase switch + { + P12S_Phase.Pangenesis => P12S_Phase.Classic_II, + _ => P12S_Phase.Classic_I, + }; + DebugMsg($"当前阶段为:{phase}", accessory); + } + + [ScriptMethod(name: "本体:一索尼,玩家索尼分组(不可控)", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(016F|017[012])$"], userControl: false)] + public void MB_Classic1_PlayerGroupRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + var id = @event.Id(); + var tid = @event.TargetId(); + var tidx = IndexHelper.getPlayerIdIndex(tid, accessory); + var tname = @event.TargetName(); + + // 四种索尼 + const uint CIRCLE = 367; + const uint CROSS = 370; + const uint TRIANGLE = 368; + const uint SQUARE = 369; + + lock (mb_Classic1_playerGroup) + { + switch (id) + { + case CIRCLE: + mb_Classic1_playerGroup[tidx] = mb_Classic1_playerGroup[tidx] + 1; + break; + case CROSS: + mb_Classic1_playerGroup[tidx] = mb_Classic1_playerGroup[tidx] + 2; + break; + case TRIANGLE: + mb_Classic1_playerGroup[tidx] = mb_Classic1_playerGroup[tidx] + 3; + break; + case SQUARE: + mb_Classic1_playerGroup[tidx] = mb_Classic1_playerGroup[tidx] + 4; + break; + default: + break; + } + } + // DebugMsg($"玩家{tidx}({tname})获得了Icon {id}", accessory); + } + + [ScriptMethod(name: "本体:一索尼,玩家AB分组(不可控)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(356[01])$"], userControl: false)] + public void MB_Classic1_PlayerBuffRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + var sid = @event.StatusID(); + var tid = @event.TargetId(); + var tidx = IndexHelper.getPlayerIdIndex(tid, accessory); + var tname = @event.TargetName(); + + // 两种BUFF + const uint ALPHA = 3560; + const uint BETA = 3561; + + lock (mb_Classic1_playerGroup) + { + switch (sid) + { + case ALPHA: + break; + case BETA: + mb_Classic1_playerGroup[tidx] = mb_Classic1_playerGroup[tidx] + 10; + break; + default: + break; + } + } + // DebugMsg($"玩家{tidx}({tname})获得了BUFF {(sid == ALPHA ? "ALPHA" : "BETA")}", accessory); + } + + [ScriptMethod(name: "本体:一索尼,元素分组(不可控)", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(1618[345])$"], userControl: false)] + public void MB_Classic1_ElementRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + var did = @event.DataId(); + var sid = @event.SourceId(); + var sname = @event.SourceName(); + var spos = @event.SourcePosition(); + + int[] position = getElementPos(spos); + lock (mb_Classic1_elements) + { + mb_Classic1_elements.Add(new ClassicElement(sid, did, position[0], position[1])); + if (mb_Classic1_elements.Count() != 12) return; + sortClassicElements(mb_Classic1_elements, accessory); + } + } + private void sortClassicElements(List elements, ScriptAccessory accessory) + { + for (int i = 0; i < 12; i++) + { + ClassicElement element = elements[i]; + if (!element.isWater()) + continue; + + // 此时element为水元素,需找临近元素 + List e_idxs = getNearbyElementIdxs(element, elements); + + DebugMsg($"正在检验水元素({element.Row}, {element.Col}),其临近有{e_idxs.Count()}个元素", accessory); + + var FiresNearby = 0; + var EarthesNearby = 0; + + foreach (var idx in e_idxs) + { + ClassicElement t_element = elements[idx]; + if (t_element.isFire()) + FiresNearby++; + if (t_element.isEarth()) + EarthesNearby++; + } + + foreach (var idx in e_idxs) + { + // 设置临近元素为目标,待检验 + ClassicElement t_element = elements[idx]; + DebugMsg($"正在检验{(t_element.isFire() ? "火元素" : "土元素")}({t_element.Row}, {t_element.Col})", accessory); + + // 若已被选择,忽略 + if (t_element.HasChosen) continue; + + // 只有一个火/土元素临近,必是其目标 + if ((FiresNearby == 1 && t_element.isFire()) || (EarthesNearby == 1 && t_element.isEarth())) + { + AssignTarget(t_element, element); + DebugMsg($"水元素({element.Row},{element.Col})找到了独苗目标{(t_element.isFire() ? "火元素" : "土元素")}({t_element.Row},{t_element.Col})", accessory); + continue; + } + + // 目标元素进一步寻找其临近元素 + List te_idxs = getNearbyElementIdxs(t_element, elements); + + // 如果其临近水元素不为1,则代表为模糊项,忽略 + var te_water_nearby = 0; + foreach (var te_idx in te_idxs) + { + if (elements[te_idx].isWater()) + te_water_nearby++; + } + if (te_water_nearby != 1) continue; + + // 目标元素为该水元素连接对象 + AssignTarget(t_element, element); + DebugMsg($"水元素({element.Row},{element.Col})找到了二次判断目标{(t_element.isFire() ? "火元素" : "土元素")}({t_element.Row},{t_element.Col})", accessory); + } + } + } + + private void AssignTarget(ClassicElement t_element, ClassicElement element) + { + t_element.TargetRow = element.Row; + t_element.TargetCol = element.Col; + t_element.TargetSid = element.Sid; + t_element.HasChosen = true; + } + + private List getNearbyElementIdxs(ClassicElement element, List elements) + { + List idxs = new List(); + for (int i = 0; i < 12; i++) + { + if (element.isNear(elements[i])) + idxs.Add(i); + } + return idxs; + } + private int[] getElementPos(Vector3 spos) + { + // 88 96 104 112 + // 80 1 2 3 4 + + // 84 92 100 + // 76 1 2 3 + int col = (int)Math.Round((spos.X - 80) / 8); + int row = (int)Math.Round((spos.Z - 76) / 8); + + return [row, col]; + } + + public class ClassicElement + { + const uint FIRE = 16183; + const uint WATER = 16184; + const uint EARTH = 16185; + public uint Sid { get; set; } + public uint Type { get; set; } + public int Row { get; set; } + public int Col { get; set; } + public int TargetRow { get; set; } = -1; + public int TargetCol { get; set; } = -1; + public uint TargetSid { get; set; } = 0; + public bool HasChosen { get; set; } = false; + public ClassicElement(uint sid, uint type, int row, int col) + { + Sid = sid; + Type = type; + Row = row; + Col = col; + } + public bool isNear(ClassicElement element) + { + int sum = Math.Abs(Row - element.Row) + Math.Abs(Col - element.Col); + return sum == 1; + } + public bool isWater() + { + return Type == WATER; + } + public bool isFire() + { + return Type == FIRE; + } + public bool isEarth() + { + return Type == EARTH; + } + } + + [ScriptMethod(name: "本体:一索尼,元素指路", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:16183"])] + public async void MB_Classic1_ElementTargetDraw(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + await Task.Delay(300); + + if (mb_Classic1_etDrawn) return; + mb_Classic1_etDrawn = true; + + var myIndex = IndexHelper.getMyIndex(accessory); + + foreach (var element in mb_Classic1_elements) + { + if (element.isWater()) continue; + drawElementRoute(element, mb_Classic1_playerGroup[myIndex], accessory); + } + } + + private void drawElementRoute(ClassicElement e, int myClassicBuff, ScriptAccessory accessory) + { + int myCol = myClassicBuff % 10; + bool isAlpha = myClassicBuff < 10; + + // 匹配条件:((元素为火,我是Alpha) 或 (元素为土,我是Beta)) 且 我的列等于目标(水元素)的列 + bool isMatched = ((e.isFire() && isAlpha) || (e.isEarth() && !isAlpha)) && myCol == e.TargetCol; + + var dp = assignDp_Element2Element(e, accessory); + dp.Color = isMatched ? posColorPlayer.V4.WithW(3f) : posColorNormal.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + + if (!isMatched) return; + + IGameObject? water = accessory.GetById(e.TargetSid); + IGameObject? fe = accessory.GetById(e.Sid); + if (water == null || fe == null) return; + + Vector3 tpos = (fe.Position - water.Position) / 8 * 3f + water.Position; + var dp0 = AssignDp.dirPos(tpos, 0, 6000, $"索尼元素指路", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + + } + private DrawPropertiesEdit assignDp_Element2Element(ClassicElement e, ScriptAccessory accessory) + { + var dp = AssignDp.drawOwner2Target(e.TargetSid, e.Sid, 0, 12000, $"索尼元素{e.Row}{e.Col}", accessory); + dp.Scale = new(2f, 4f); + dp.Color = posColorNormal.V4.WithW(1.5f); + return dp; + } + + // 紫线出现Tether 0001,元素钢铁 + [ScriptMethod(name: "本体:一索尼,元素毁灭与射线指路预备", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0001"])] + public void MB_Classic1_RayDirPrepared(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + + if (mb_Classic1_implodeDrawn) return; + mb_Classic1_implodeDrawn = true; + + foreach (var element in mb_Classic1_elements) + { + var dp = AssignDp.drawCircle(element.Sid, 0, 12000, $"自我毁灭{element.Sid}", accessory); + dp.Scale = new(4f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + var myIndex = IndexHelper.getMyIndex(accessory); + var myClassicIndex = getMyClassicIndex(myIndex); + drawRayDirPrepared(myClassicIndex, true, accessory); + } + + private int getMyClassicIndex(int myIndex) + { + var myClassicBuff = mb_Classic1_playerGroup[myIndex]; + bool isAlpha = myClassicBuff < 10; + var myClassicIndex = ((myClassicBuff % 10) - 1) * 2 + (isAlpha ? 0 : 1); + return myClassicIndex; + } + + private void drawRayDirPrepared(int myClassicIndex, bool isDanger, ScriptAccessory accessory) + { + Vector3[] sonyPos = new Vector3[8]; + sonyPos[0] = new(84, 0, 88); + sonyPos[1] = new(84, 0, 96); + sonyPos[2] = new(92, 0, 88); + sonyPos[3] = new(92, 0, 96); + sonyPos[4] = new(108, 0, 88); + sonyPos[5] = new(108, 0, 96); + sonyPos[6] = new(116, 0, 88); + sonyPos[7] = new(116, 0, 96); + + for (int i = 0; i < sonyPos.Count(); i++) + { + if (isDanger) + { + var dp = AssignDp.drawStatic(sonyPos[i], 0, 0, 12000, $"索尼射线待命位置{i}", accessory); + dp.Scale = new(1f); + dp.Color = i == myClassicIndex ? posColorPlayer.V4.WithW(3f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + if (i != myClassicIndex) continue; + var dp0 = AssignDp.dirPos(sonyPos[i], 0, 12000, $"{(isDanger ? $"索尼射线待命位置指路{i}" : $"索尼射线位置指路{i}")}", accessory); + dp0.Color = isDanger ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + } + + [ScriptMethod(name: "本体:一索尼,元素毁灭绘图消失(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:33587"], userControl: false)] + public void MB_Classic1_ImplodeRemove(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + accessory.Method.RemoveDraw($"^(自我毁灭.*)$"); + } + + // 绿线消失,索尼射线指路 + [ScriptMethod(name: "本体:一索尼,射线范围与指路", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:3588"])] + public void MB_Classic1_RayDir(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + if (mb_Classic1_RayDirDrawn) return; + mb_Classic1_RayDirDrawn = true; + + accessory.Method.RemoveDraw($"^(索尼射线待命位置指路.*)$"); + + var myIndex = IndexHelper.getMyIndex(accessory); + var myClassicIndex = getMyClassicIndex(myIndex); + drawRayDirPrepared(myClassicIndex, false, accessory); + drawPalladianRay(new(92, 0, 92), accessory); + drawPalladianRay(new(108, 0, 92), accessory); + } + + private void drawPalladianRay(Vector3 pos, ScriptAccessory accessory) + { + for (uint i = 0; i < 4; i++) + { + var dp = AssignDp.drawTargetOrder(0, i + 1, 0, 12000, $"帕拉斯射线{i}", accessory); + dp.Position = pos; + dp.Scale = new(20f); + dp.Radian = float.Pi / 6; + // dp.Color = accessory.Data.DefaultDangerColor.WithW(0.6f); + dp.Color = ColorHelper.colorPink.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + } + + [ScriptMethod(name: "本体:一索尼,射线与指路删除(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:33572"], userControl: false)] + public void MB_Classic1_RayDirRemove(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Classic_I) return; + accessory.Method.RemoveDraw($"^(帕拉斯射线.*)$"); + accessory.Method.RemoveDraw($"^(索尼射线待命位置.*)$"); + accessory.Method.RemoveDraw($"^(索尼射线位置指路.*)$"); + } + + #endregion + + #region 本体:一风火 + + [ScriptMethod(name: "本体:阶段转换风火(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33592"], userControl: false)] + public void MB_PhaseChange_Caloric(Event @event, ScriptAccessory accessory) + { + phase = phase switch + { + P12S_Phase.Classic_II => P12S_Phase.Caloric_II, + _ => P12S_Phase.Caloric_I, + }; + mb_Caloric_phase = 0; + DebugMsg($"当前阶段为:{phase}", accessory); + } + + [ScriptMethod(name: "本体:一风火,初始点名记录(不可控)", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:012F"], userControl: false)] + public async void MB_Caloric_FirstDir(Event @event, ScriptAccessory accessory) + { + await Task.Delay(200); + + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase > 2) return; + + var tid = @event.TargetId(); + var tidx = IndexHelper.getPlayerIdIndex(tid, accessory); + mb_Caloric_isFirstTarget[tidx] = true; + mb_Caloric_WindPriority[tidx]++; + if (tidx >= 4) + mb_Caloric_WindPriority[tidx]++; + // 1 左上,2 右上,3 右下,4 左下 + + mb_Caloric_phase++; + } + + [ScriptMethod(name: "本体:一风火,初始指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33597"])] + public async void MB_Caloric_FirstStack(Event @event, ScriptAccessory accessory) + { + await Task.Delay(300); + + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase > 2) return; + + var myIndex = IndexHelper.getMyIndex(accessory); + var tid = @event.TargetId(); + drawCaloricStack(tid, mb_Caloric_isFirstTarget[myIndex], accessory); + drawCaloricFirstDir(accessory); + } + + private void drawCaloricStack(uint owner_id, bool isDanger, ScriptAccessory accessory) + { + var dp = AssignDp.drawCircle(owner_id, 0, 8000, $"一风火分摊{owner_id}", accessory); + dp.Scale = new(4); + dp.Color = isDanger ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void drawCaloricFirstDir(ScriptAccessory accessory) + { + var myIndex = IndexHelper.getMyIndex(accessory); + Vector3 SAFE = new Vector3(100, 0, 97.5f); + Vector3 UPLEFT = new Vector3(99, 0, 89); + Vector3 UPRIGHT = new Vector3(101, 0, 89); + + var isSafe = !mb_Caloric_isFirstTarget[myIndex]; + var pos = isSafe ? SAFE : (myIndex < 4 ? UPLEFT : UPRIGHT); + var dp0 = AssignDp.dirPos(pos, 0, 8000, $"一风火初始指路{myIndex}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + + [ScriptMethod(name: "本体:一风火转阶段,四组分摊(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:33592"], userControl: false)] + public void MB_PhaseChange_Caloric_PartnerStack(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase > 2) return; + mb_Caloric_phase = 10; + accessory.Method.RemoveDraw($"^(一风火分摊.*)$"); + accessory.Method.RemoveDraw($"^(一风火初始指路.*)$"); + } + + [ScriptMethod(name: "本体:一风火buff记录(不可控)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(359[01])$"], userControl: false)] + public void MB_Caloric_BuffRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 10) return; + + var tid = @event.TargetId(); + var tidx = IndexHelper.getPlayerIdIndex(tid, accessory); + var sid = @event.StatusID(); + + // const uint FIRE = 3590; + const uint WIND = 3591; + mb_Caloric_isWind[tidx] = sid == WIND; + } + + [ScriptMethod(name: "本体:一风火,四组分摊范围", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3590"])] + public async void MB_Caloric_PartnerStack(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 10) return; + mb_Caloric_phase++; + if (mb_Caloric_ParnterStackDrawn) return; + mb_Caloric_ParnterStackDrawn = true; + + await Task.Delay(200); + + DebugMsg($"开始绘制火分摊范围 {mb_Caloric_phase}", accessory); + + var myStackPos = getMyStackPos(accessory); + for (int i = 0; i < 8; i++) + { + if (mb_Caloric_isWind[i]) continue; + bool isMyPartner = myStackPos == mb_Caloric_FirePriority[i]; + drawCaloricPartnerStack(accessory.Data.PartyList[i], isMyPartner, accessory); + } + } + + private int getMyStackPos(ScriptAccessory accessory) + { + var myIndex = IndexHelper.getMyIndex(accessory); + var myStackPos = (mb_Caloric_WindPriority[myIndex] + mb_Caloric_FirePriority[myIndex]) % 10; + return myStackPos; + } + + private void drawCaloricPartnerStack(uint owner_id, bool isMyPartner, ScriptAccessory accessory) + { + var dp = AssignDp.drawCircle(owner_id, 0, 12000, $"一风火四组分摊{owner_id}", accessory); + dp.Scale = new(4); + dp.Color = isMyPartner ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "本体:一风火,四组分摊风火指路", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3591"])] + public async void MB_Caloric_PartnerStackDir(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 10) return; + mb_Caloric_phase++; + if (mb_Caloric_ParnterStackDirDrawn) return; + mb_Caloric_ParnterStackDirDrawn = true; + + await Task.Delay(100); + + DebugMsg($"开始执行一次风火优先级计算 {mb_Caloric_phase}", accessory); + calcCaloricPriority(); + string WindPriorityStr = string.Join(", ", mb_Caloric_WindPriority); + DebugMsg($"风优先级:{WindPriorityStr}", accessory); + string FirePriorityStr = string.Join(", ", mb_Caloric_FirePriority); + DebugMsg($"火优先级:{FirePriorityStr}", accessory); + + var myIndex = IndexHelper.getMyIndex(accessory); + var myStackPos = getMyStackPos(accessory); + Vector3 stackPos = myStackPos switch + { + 1 => new(97.5f, 0, 92.5f), + 2 => new(102.5f, 0, 92.5f), + 3 => new(102.5f, 0, 97.5f), + 4 => new(97.5f, 0, 97.5f), + _ => new(100, 0, 100) + }; + + if (mb_Caloric_isWind[myIndex] && mb_Caloric_WindPriority[myIndex] <= 2) + stackPos = stackPos - new Vector3(0, 0, 3.5f); + + var dp = AssignDp.dirPos(stackPos, 0, 12000, $"一风火就位{myStackPos}{mb_Caloric_isWind[myIndex]}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private void calcCaloricPriority() + { + for (var i = 0; i < 8; i++) + { + if (mb_Caloric_isWind[i]) + { + if (mb_Caloric_WindPriority[i] != 0) continue; + mb_Caloric_WindPriority[i] = mb_Caloric_WindPriority.Max() + 1; + } + else + { + if (mb_Caloric_FirePriority[i] != 0) continue; + mb_Caloric_FirePriority[i] = mb_Caloric_FirePriority.Max() + 1; + } + } + } + + [ScriptMethod(name: "本体:一风火转阶段,风击退(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:33594"], userControl: false)] + public void MB_PhaseChange_Caloric_WindKnockBack(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 10) return; + mb_Caloric_phase = 20; + accessory.Method.RemoveDraw($"^(一风火就位.*)$"); + accessory.Method.RemoveDraw($"^(一风火四组分摊.*)$"); + } + + [ScriptMethod(name: "本体:一风火,二次火分摊记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3590"], userControl: false)] + public void MB_Caloric_SecondBuffRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 20) return; + + var tid = @event.TargetId(); + var tidx = IndexHelper.getPlayerIdIndex(tid, accessory); + // 二次火分摊一定在一次火中选 + lock (mb_Caloric_FirePriority) + { + mb_Caloric_FirePriority[tidx] += 10; + } + } + + [ScriptMethod(name: "本体:一风火,二次火分摊指路", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3590"])] + public async void MB_Caloric_SecondPartnerStackDir(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 20) return; + mb_Caloric_phase++; + + if (mb_Caloric_SecondParnterStackDirDrawn) return; + mb_Caloric_SecondParnterStackDirDrawn = true; + + await Task.Delay(100); + + DebugMsg($"开始执行二次风火优先级计算 {mb_Caloric_phase}", accessory); + calcCaloricPrioritySecond(); + + string WindPriorityStr = string.Join(", ", mb_Caloric_WindPriority); + DebugMsg($"风优先级:{WindPriorityStr}", accessory); + string FirePriorityStr = string.Join(", ", mb_Caloric_FirePriority); + DebugMsg($"火优先级:{FirePriorityStr}", accessory); + + var myIndex = IndexHelper.getMyIndex(accessory); + var myStackPos = getMyStackPos(accessory); + + Vector3 stackPos = myStackPos switch + { + 1 => mb_Caloric_isWind[myIndex] ? new(93.5f, 0, 85.5f) : new(97.5f, 0, 92.5f), + 2 => mb_Caloric_isWind[myIndex] ? new(106.5f, 0, 85.5f) : new(102.5f, 0, 92.5f), + 3 => new(106.5f, 0, 100.5f), + 4 => new(93.5f, 0, 100.5f), + _ => new(100, 0, 100) + }; + + var dp = AssignDp.dirPos(stackPos, 0, 12000, $"一风火二次就位{myStackPos}{mb_Caloric_isWind[myIndex]}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private void calcCaloricPrioritySecond() + { + bool needSwap = false; + var fireTargets = mb_Caloric_FirePriority.Where(x => x > 10).Take(2).ToList(); + + // 同左同右 + if (fireTargets.Sum() % 10 == 5) + needSwap = true; + + for (int i = 0; i < 8; i++) + { + mb_Caloric_FirePriority[i] = (mb_Caloric_FirePriority[i] % 10) switch + { + 1 => mb_Caloric_FirePriority[i], + 2 => mb_Caloric_FirePriority[i], + 3 => (mb_Caloric_FirePriority[i] > 10 ? 10 : 0) + (needSwap ? 1 : 2), + 4 => (mb_Caloric_FirePriority[i] > 10 ? 10 : 0) + (needSwap ? 2 : 1), + _ => mb_Caloric_FirePriority[i], + }; + } + } + + [ScriptMethod(name: "本体:一风火,二次分摊范围", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3590"])] + public async void MB_Caloric_SecondPartnerStack(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 20) return; + mb_Caloric_phase++; + if (mb_Caloric_SecondParnterStackDrawn) return; + mb_Caloric_SecondParnterStackDrawn = true; + + await Task.Delay(200); + + DebugMsg($"开始绘制火分摊范围 {mb_Caloric_phase}", accessory); + + var myStackPos = getMyStackPos(accessory); + for (int i = 0; i < 8; i++) + { + if (mb_Caloric_isWind[i]) continue; + if (mb_Caloric_FirePriority[i] < 10) continue; + bool isMyPartner = myStackPos == mb_Caloric_FirePriority[i] % 10; + drawCaloricPartnerStack(accessory.Data.PartyList[i], isMyPartner, accessory); + } + } + + [ScriptMethod(name: "本体:一风火,二次环风范围", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:3590"])] + public async void MB_Caloric_SecondWindDonut(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 20) return; + mb_Caloric_phase++; + if (mb_Caloric_SecondWindDonutDrawn) return; + mb_Caloric_SecondWindDonutDrawn = true; + + await Task.Delay(200); + + DebugMsg($"开始绘制环风范围 {mb_Caloric_phase}", accessory); + + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + if (!mb_Caloric_isWind[i]) continue; + drawWindCircle(accessory.Data.PartyList[i], accessory); + } + } + + private void drawWindCircle(uint owner_id, ScriptAccessory accessory) + { + var dp = AssignDp.drawCircle(owner_id, 0, 12000, $"一风火环风{owner_id}", accessory); + dp.Scale = new(7); + // dp.Color = accessory.Data.DefaultDangerColor; + dp.Color = ColorHelper.colorLightBlue.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "本体:一风火结束(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:33595"], userControl: false)] + public void MB_PhaseChange_Caloric_End(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Caloric_I) return; + if (mb_Caloric_phase < 20) return; + mb_Caloric_phase = 30; + accessory.Method.RemoveDraw($"^(一风火四组分摊.*)$"); + accessory.Method.RemoveDraw($"^(一风火环风.*)$"); + accessory.Method.RemoveDraw($"^(一风火二次就位.*)$"); + } + + #endregion + + #region 本体:一地火 + + [ScriptMethod(name: "本体:阶段转换地火(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33566"], userControl: false)] + public void MB_PhaseChange_Exflare(Event @event, ScriptAccessory accessory) + { + phase = P12S_Phase.Exflare; + DebugMsg($"当前阶段为:{phase}", accessory); + } + + [ScriptMethod(name: "本体:地火预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33567"])] + public void MB_Exflare(Event @event, ScriptAccessory accessory) + { + var spos = @event.SourcePosition(); + var srot = @event.SourceRotation(); + + Vector3[] exflarePos = new Vector3[6]; + for (int i = 0; i < 6; i++) + { + exflarePos[i] = DirectionCalc.ExtendPoint(spos, DirectionCalc.BaseInnGame2DirRad(srot), 8 * i); + } + + const int CAST_TIME = 6000; + const int INTERVAL_TIME = 2000; + + for (int i = 0; i < 6; i++) + { + var destoryAt = i == 0 ? CAST_TIME : INTERVAL_TIME; + var delay = i == 0 ? 0 : CAST_TIME + (i - 1) * INTERVAL_TIME; + // 本体地火 + drawExflare(exflarePos[i], delay, destoryAt, accessory); + // 预警地火 + if (i < 5) + drawExflareWarn(exflarePos[i + 1], 1, delay, destoryAt, accessory); + if (i < 4) + drawExflareWarn(exflarePos[i + 2], 2, delay, destoryAt, accessory); + } + } + + private void drawExflare(Vector3 spos, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = AssignDp.drawStatic(spos, 0, delay, destoryAt, $"地火{spos}", accessory); + dp.Scale = new(6f); + dp.Color = exflareColor.V4.WithW(1.5f); + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private void drawExflareWarn(Vector3 spos, int adv, int delay, int destoryAt, ScriptAccessory accessory) + { + const int INTERVAL_TIME = 2000; + + var destroy_add = INTERVAL_TIME * (adv - 1); + var dp = AssignDp.drawStatic(spos, 0, delay, destoryAt + destroy_add, $"地火{spos}", accessory); + dp.Scale = new(6f); + dp.Color = exflareWarnColor.V4.WithW(0.8f / adv); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "本体:地火核爆安全区记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:34435"], userControl: false)] + public void MB_ExflarePosRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Exflare) return; + var spos = @event.SourcePosition(); + var posidx = DirectionCalc.PositionRoundToDirs(spos, new(100, 0, 95), 4); + mb_Exflare_FlarePos[posidx] = true; + } + + [ScriptMethod(name: "本体:地火安全区指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:34435"])] + public async void MB_ExflareDir(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Exflare) return; + var spos = @event.SourcePosition(); + if (mb_Exflare_DirDrawn) return; + mb_Exflare_DirDrawn = true; + + await Task.Delay(100); + if (mb_Exflare_FlarePos.Count(x => x) != 2) return; + DebugMsg($"{(mb_Exflare_FlarePos[0] ? "上有地火,左右安全" : "左有地火,上下安全")}", accessory); + + Vector3[] ExflareDir = new Vector3[2]; + var myIndex = IndexHelper.getMyIndex(accessory); + + // 上有地火,左右安全 + ExflareDir[0] = getExflareFirstSafePos(mb_Exflare_FlarePos[0], myIndex); + ExflareDir[1] = getExflareSecondSafePos(mb_Exflare_FlarePos[0], myIndex); + drawExflareDir(ExflareDir[0], ExflareDir[1], accessory); + } + + private Vector3 getExflareFirstSafePos(bool isLeftSafe, int myIndex) + { + Vector3[] ExflareSafePos = new Vector3[4]; + ExflareSafePos[0] = new(98, 0, 81); // 上 + ExflareSafePos[1] = new(119, 0, 92); // 右 + ExflareSafePos[2] = new(102, 0, 109); // 下 + ExflareSafePos[3] = new(81, 0, 98); // 左 + + int safePosIdx; + if (isLeftSafe) + safePosIdx = (myIndex % 2 == 0) ? 3 : 1; // MT组为偶数,余数为0 + else + safePosIdx = (myIndex % 4 < 2) ? 0 : 2; // 近战组idx除以4,余数为0、1 + return ExflareSafePos[safePosIdx]; + } + + private Vector3 getExflareSecondSafePos(bool isLeftSafe, int myIndex) + { + const int CENTER_Z = 95; + const int CENTER_X = 100; + + Vector3[] ExflareSpreadPos = new Vector3[16]; + ExflareSpreadPos[0] = new(81, 0, 81); + ExflareSpreadPos[1] = DirectionCalc.FoldPointLR(ExflareSpreadPos[0], CENTER_X); + ExflareSpreadPos[4] = new(93, 0, 81); + ExflareSpreadPos[5] = DirectionCalc.FoldPointLR(ExflareSpreadPos[4], CENTER_X); + ExflareSpreadPos[2] = DirectionCalc.FoldPointUD(ExflareSpreadPos[4], CENTER_Z); + ExflareSpreadPos[3] = DirectionCalc.FoldPointUD(ExflareSpreadPos[5], CENTER_Z); + ExflareSpreadPos[6] = DirectionCalc.FoldPointUD(ExflareSpreadPos[0], CENTER_Z); + ExflareSpreadPos[7] = DirectionCalc.FoldPointUD(ExflareSpreadPos[1], CENTER_Z); + + ExflareSpreadPos[8] = new(81, 0, 81); + ExflareSpreadPos[9] = DirectionCalc.FoldPointLR(ExflareSpreadPos[8], CENTER_X); + ExflareSpreadPos[12] = new(81, 0, 90); + ExflareSpreadPos[13] = DirectionCalc.FoldPointLR(ExflareSpreadPos[12], CENTER_X); + ExflareSpreadPos[10] = DirectionCalc.FoldPointUD(ExflareSpreadPos[12], CENTER_Z); + ExflareSpreadPos[11] = DirectionCalc.FoldPointUD(ExflareSpreadPos[13], CENTER_Z); + ExflareSpreadPos[14] = DirectionCalc.FoldPointUD(ExflareSpreadPos[8], CENTER_Z); + ExflareSpreadPos[15] = DirectionCalc.FoldPointUD(ExflareSpreadPos[9], CENTER_Z); + + int safePosIdx; + if (isLeftSafe) + safePosIdx = myIndex + 8; // 8-15为左右分散 + else + safePosIdx = myIndex; // 0-7为上下分散 + return ExflareSpreadPos[safePosIdx]; + } + + private void drawExflareDir(Vector3 safePos, Vector3 spreadPos, ScriptAccessory accessory) + { + var dp = AssignDp.dirPos(safePos, 0, 7000, $"地火核爆安全", accessory); + var dp0 = AssignDp.dirPos2Pos(safePos, spreadPos, 0, 7000, $"即将分散位置", accessory); + dp0.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + + var dp1 = AssignDp.dirPos(spreadPos, 7000, 3000, $"分散位置", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + + #endregion + + #region 本体:黑白塔 + + [ScriptMethod(name: "本体:阶段转换黑白塔(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33599"], userControl: false)] + public void MB_PhaseChange_Pangenesis(Event @event, ScriptAccessory accessory) + { + phase = P12S_Phase.Pangenesis; + DebugMsg($"当前阶段为:{phase}", accessory); + } + + [ScriptMethod(name: "本体:黑白塔排队提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33599"])] + public async void MB_Pangenesis_InitPosition(Event @event, ScriptAccessory accessory) + { + await Task.Delay(100); + if (phase != P12S_Phase.Pangenesis) return; + accessory.Method.TextInfo($"横向排队", 5000); + } + + [ScriptMethod(name: "本体:黑白塔BUFF记录(不可控)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3593|357[67])$"], userControl: false)] + public async void MB_Pangenesis_BuffRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Pangenesis) return; + if (mb_Pangenesis_phase[0]) return; + var tid = @event.TargetId(); + var tidx = IndexHelper.getPlayerIdIndex(tid, accessory); + var sid = @event.StatusID(); + var stc = @event.StackCount(); + var dur = @event.DurationMilliseconds(); + + const uint DNA = 3593; + const uint WHITE = 3576; + const uint BLACK = 3577; + const uint LONG = 20000; + const uint SHORT = 16000; + + // DebugMsg($"检测到TIDX {tidx}, SID {sid}, STC {stc}, DUR {dur}", accessory); + + switch (sid) + { + case DNA: + mb_Pangenesis_hasFactor[tidx] = true; + mb_Pangenesis_isTwo[tidx] = stc == 2; + // DebugMsg($"玩家{tidx}:{(sid == DNA ? "因子" : "无因子")}, {(stc == 2 ? "2层": "1层")}", accessory); + break; + case WHITE: + case BLACK: + mb_Pangenesis_isWhite[tidx] = sid == WHITE; + mb_Pangenesis_isLong[tidx] = dur == LONG; + mb_Pangenesis_shouldWhiteTower[tidx] = sid == BLACK; + // DebugMsg($"玩家{tidx}:{(sid == WHITE ? "白Buff" : "黑buff")}, {(dur == LONG ? "长": "短")}", accessory); + break; + default: + break; + } + + + await Task.Delay(100); + mb_Pangenesis_phase[0] = true; + // DebugMsg($"黑白塔初始BUFF记录封锁", accessory); + } + + [ScriptMethod(name: "本体:黑白塔无极性组就位", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:33602"])] + public void MB_Pangenesis_NonpolarityPos(Event @event, ScriptAccessory accessory) + { + var FirstLeftPlayerIdx = getSpecificTowerPlayer(true, false, false, false, true); + var FirstRightPlayerIdx = getSpecificTowerPlayer(true, false, false, false, false); + var SecondUpLeftPlayerIdx = getSpecificTowerPlayer(false, false, false, false, true); + var SecondUpRightPlayerIdx = getSpecificTowerPlayer(false, false, false, false, false); + + mb_Pangenesis_TowerIdxOrder[0] = FirstLeftPlayerIdx; + mb_Pangenesis_TowerIdxOrder[4] = FirstRightPlayerIdx; + mb_Pangenesis_TowerIdxOrder[2] = SecondUpLeftPlayerIdx; + mb_Pangenesis_TowerIdxOrder[6] = SecondUpRightPlayerIdx; + mb_Pangenesis_TowerIdxSecondOrder[0] = SecondUpLeftPlayerIdx; + mb_Pangenesis_TowerIdxSecondOrder[4] = SecondUpRightPlayerIdx; + + DebugMsg($"左一塔{IndexHelper.getPlayerJobByIndex(FirstLeftPlayerIdx)}就位。", accessory); + DebugMsg($"右一塔{IndexHelper.getPlayerJobByIndex(FirstRightPlayerIdx)}就位。", accessory); + DebugMsg($"左二上塔{IndexHelper.getPlayerJobByIndex(SecondUpLeftPlayerIdx)}就位。", accessory); + DebugMsg($"右二上塔{IndexHelper.getPlayerJobByIndex(SecondUpRightPlayerIdx)}就位。", accessory); + + const int CENTER_X = 100; + const int CENTER_Z = 91; + + Vector3 FIRST_LEFT = new Vector3(85, 0, 91); + Vector3 FIRST_RIGHT = DirectionCalc.FoldPointLR(FIRST_LEFT, CENTER_X); + Vector3 SECOND_UP_LEFT = new Vector3(90, 0, 88); + Vector3 SECOND_UP_RIGHT = DirectionCalc.FoldPointLR(SECOND_UP_LEFT, CENTER_X); + + var myIndex = IndexHelper.getMyIndex(accessory); + if (myIndex == FirstLeftPlayerIdx) + drawPangenesisDir(FIRST_LEFT, FirstLeftPlayerIdx, 0, 9000, accessory); + // var dp_FirstLeft = assignDp_TowerDir(FIRST_LEFT, FirstLeftPlayerIdx, 0, 9000, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp_FirstLeft); + if (myIndex == FirstRightPlayerIdx) + drawPangenesisDir(FIRST_RIGHT, FirstRightPlayerIdx, 0, 9000, accessory); + // var dp_FirstRight = assignDp_TowerDir(FIRST_RIGHT, FirstRightPlayerIdx, 0, 9000, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp_FirstRight); + if (myIndex == SecondUpLeftPlayerIdx) + drawPangenesisDir(SECOND_UP_LEFT, SecondUpLeftPlayerIdx, 0, 14000, accessory); + // var dp_SecondUpLeft = assignDp_TowerDir(SECOND_UP_LEFT, SecondUpLeftPlayerIdx, 0, 14000, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp_SecondUpLeft); + if (myIndex == SecondUpRightPlayerIdx) + drawPangenesisDir(SECOND_UP_RIGHT, SecondUpRightPlayerIdx, 0, 14000, accessory); + // var dp_SecondUpRight = assignDp_TowerDir(SECOND_UP_RIGHT, SecondUpRightPlayerIdx, 0, 14000, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp_SecondUpRight); + } + + [ScriptMethod(name: "本体:黑白塔数量记录(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3360[34])$"], userControl: false)] + public void MB_Pangenesis_TowerNumRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Pangenesis) return; + mb_Pangenesis_towerNum++; + } + + [ScriptMethod(name: "本体:黑白塔第一步", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3360[34])$"])] + public async void MB_Pangenesis_FirstPlace(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Pangenesis) return; + if (mb_Pangenesis_phase[1]) return; + + // 等塔数记录完毕 + await Task.Delay(100); + if (mb_Pangenesis_towerNum > 2) return; + + var myIndex = IndexHelper.getMyIndex(accessory); + + bool isLeftTower = false; + bool isWhiteTower = false; + bool needWhiteBuff; + + const int CENTER_X = 100; + const uint WHITE_TOWER = 33603; + const uint BLACK_TOWER = 33604; + + var aid = @event.ActionId(); + var tpos = @event.TargetPosition(); + + if (tpos.X < CENTER_X) + isLeftTower = true; + if (aid == WHITE_TOWER) + isWhiteTower = true; + needWhiteBuff = !isWhiteTower; + + // 只有出现了塔的颜色才能判断的剩余四人 + mb_Pangenesis_shouldWhiteTower[mb_Pangenesis_TowerIdxOrder[isLeftTower ? 0 : 4]] = isWhiteTower; + mb_Pangenesis_shouldWhiteTower[mb_Pangenesis_TowerIdxOrder[isLeftTower ? 2 : 6]] = !isWhiteTower; + + // 边1塔:2层因子+短黑白BUFF + var FirstPlayerIdx = getSpecificTowerPlayer(true, true, false, needWhiteBuff, true); + mb_Pangenesis_TowerIdxOrder[isLeftTower ? 1 : 5] = FirstPlayerIdx; + + DebugMsg($"{(isLeftTower ? "左一塔" : "右一塔")}为{(isWhiteTower ? "白" : "黑")},{IndexHelper.getPlayerJobByIndex(FirstPlayerIdx)}去踩。", accessory); + + if (myIndex == FirstPlayerIdx) + drawPangenesisDir(tpos, FirstPlayerIdx, 0, 5000, accessory); + // var dp = assignDp_TowerDir(tpos, FirstPlayerIdx, 0, 5000, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + const int CENTER_Z = 91; + Vector3 SECOND_UP_LEFT = new Vector3(90, 0, 88); + Vector3 SECOND_UP_RIGHT = DirectionCalc.FoldPointLR(SECOND_UP_LEFT, CENTER_X); + Vector3 SECOND_DOWN_LEFT = DirectionCalc.FoldPointUD(SECOND_UP_LEFT, CENTER_Z); + Vector3 SECOND_DOWN_RIGHT = DirectionCalc.FoldPointUD(SECOND_UP_RIGHT, CENTER_Z); + + // 下2塔:2层因子+长黑白BUFF + var SecondDownPlayerIdx = getSpecificTowerPlayer(true, true, true, needWhiteBuff, true); + mb_Pangenesis_TowerIdxOrder[isLeftTower ? 3 : 7] = SecondDownPlayerIdx; + mb_Pangenesis_TowerIdxSecondOrder[isLeftTower ? 2 : 6] = SecondDownPlayerIdx; + + DebugMsg($"{(isLeftTower ? "左二下塔" : "右二下塔")}为{(isWhiteTower ? "白" : "黑")},{IndexHelper.getPlayerJobByIndex(SecondDownPlayerIdx)}去踩。", accessory); + + if (myIndex == SecondDownPlayerIdx) + drawPangenesisDir(isLeftTower ? SECOND_DOWN_LEFT : SECOND_DOWN_RIGHT, SecondDownPlayerIdx, 0, 10000, accessory); + // var dp = assignDp_TowerDir(isLeftTower ? SECOND_DOWN_LEFT : SECOND_DOWN_RIGHT, SecondDownPlayerIdx, 0, 10000, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + await Task.Delay(100); + mb_Pangenesis_phase[1] = true; + DebugMsg($"=== 黑白塔第一步结束 ===", accessory); + } + + [ScriptMethod(name: "本体:黑白塔转换塔色(不可控)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(357[67])$"], userControl: false)] + public void MB_Pangenesis_ChangePolar(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Pangenesis) return; + if (mb_Pangenesis_towerNum < 2) return; + // 后续过程中,只要被附加黑白buff,就需要转换塔色 + var tid = @event.TargetId(); + var tidx = IndexHelper.getPlayerIdIndex(tid, accessory); + mb_Pangenesis_shouldWhiteTower[tidx] = !mb_Pangenesis_shouldWhiteTower[tidx]; + } + + [ScriptMethod(name: "本体:黑白塔第二步", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3360[34])$"])] + public async void MB_Pangenesis_SecondPlace(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Pangenesis) return; + if (mb_Pangenesis_phase[2]) return; + + // 等塔数记录完毕 + await Task.Delay(100); + if (mb_Pangenesis_towerNum <= 2) return; + if (mb_Pangenesis_towerNum > 6) return; + + // 等极性buff记录完毕 + await Task.Delay(1000); + + var myIndex = IndexHelper.getMyIndex(accessory); + + bool isLeftTower = false; + bool isWhiteTower = false; + bool isUpTower = false; + + const int CENTER_X = 100; + const int CENTER_Z = 91; + const uint WHITE_TOWER = 33603; + const uint BLACK_TOWER = 33604; + + var aid = @event.ActionId(); + var tpos = @event.TargetPosition(); + + if (tpos.X < CENTER_X) + isLeftTower = true; + if (tpos.Z < CENTER_Z) + isUpTower = true; + if (aid == WHITE_TOWER) + isWhiteTower = true; + + // spos刷出了黑塔,判断isLeft + // 如果是isLeft,踩黑塔的人应该是踩左一塔的某一人,两人都要进行判断,即 mb_Pangenesis_TowerIdxOrder[0] 与 mb_Pangenesis_TowerIdxOrder[1] + // 如果not isLeft,判断mb_Pangenesis_TowerIdxOrder[4] 与 mb_Pangenesis_TowerIdxOrder[5] + int TowerOrderPlayerIdx; + + if (isLeftTower) + { + if ((mb_Pangenesis_shouldWhiteTower[mb_Pangenesis_TowerIdxOrder[0]] && isWhiteTower) || (!mb_Pangenesis_shouldWhiteTower[mb_Pangenesis_TowerIdxOrder[0]] && !isWhiteTower)) + TowerOrderPlayerIdx = 0; + else + TowerOrderPlayerIdx = 1; + } + else + { + if ((mb_Pangenesis_shouldWhiteTower[mb_Pangenesis_TowerIdxOrder[4]] && isWhiteTower) || (!mb_Pangenesis_shouldWhiteTower[mb_Pangenesis_TowerIdxOrder[4]] && !isWhiteTower)) + TowerOrderPlayerIdx = 4; + else + TowerOrderPlayerIdx = 5; + } + + int playerIdx = mb_Pangenesis_TowerIdxOrder[TowerOrderPlayerIdx]; + int SecondTowerIdx = 1 + (isLeftTower ? 0 : 4) + (isUpTower ? 0 : 2); + mb_Pangenesis_TowerIdxSecondOrder[SecondTowerIdx] = playerIdx; + + DebugMsg($"{(isLeftTower ? "左二" : "右二")}的{(isUpTower ? "上" : "下")}{(isWhiteTower ? "白" : "黑")}塔,{IndexHelper.getPlayerJobByIndex(playerIdx)}去踩。", accessory); + + if (myIndex == playerIdx) + drawPangenesisDir(tpos, playerIdx, 0, 3900, accessory); + // var dp = assignDp_TowerDir(tpos, playerIdx, 0, 3900, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + await Task.Delay(100); + mb_Pangenesis_phase[2] = true; + DebugMsg($"=== 黑白塔第二步结束 ===", accessory); + } + + [ScriptMethod(name: "本体:黑白塔第三步", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3360[34])$"])] + public async void MB_Pangenesis_ThirdPlace(Event @event, ScriptAccessory accessory) + { + if (phase != P12S_Phase.Pangenesis) return; + if (mb_Pangenesis_phase[3]) return; + + // 等塔数记录完毕 + await Task.Delay(100); + if (mb_Pangenesis_towerNum <= 6) return; + if (mb_Pangenesis_towerNum > 10) return; + + // 等极性buff记录完毕 + await Task.Delay(1000); + + var myIndex = IndexHelper.getMyIndex(accessory); + + bool isLeftTower = false; + bool isWhiteTower = false; + bool isUpTower = false; + + const int CENTER_X = 100; + const int CENTER_Z = 91; + const uint WHITE_TOWER = 33603; + const uint BLACK_TOWER = 33604; + + var aid = @event.ActionId(); + var tpos = @event.TargetPosition(); + + if (tpos.X < CENTER_X) + isLeftTower = true; + if (tpos.Z < CENTER_Z) + isUpTower = true; + if (aid == WHITE_TOWER) + isWhiteTower = true; + + for (int i = 0; i < 4; i++) + { + var idx = isLeftTower ? i : i + 4; + var playerIdx = mb_Pangenesis_TowerIdxSecondOrder[idx]; + + if ((mb_Pangenesis_shouldWhiteTower[playerIdx] && isWhiteTower) || (!mb_Pangenesis_shouldWhiteTower[playerIdx] && !isWhiteTower)) + { + DebugMsg($"{(isLeftTower ? "左三" : "右三")}的{(isUpTower ? "上" : "下")}{(isWhiteTower ? "白" : "黑")}塔,{IndexHelper.getPlayerJobByIndex(playerIdx)}去踩。", accessory); + + if (myIndex != playerIdx) continue; + drawPangenesisDir(tpos, playerIdx, 0, 3900, accessory); + // var dp = assignDp_TowerDir(tpos, playerIdx, 0, 3900, accessory); + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + // var dp0 = AssignDp.drawStatic(tpos, 0, 0, 3900, $"黑白塔{playerIdx}", accessory); + // dp0.Scale = new(3f); + // dp0.Color = accessory.Data.DefaultSafeColor; + // accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + } + + await Task.Delay(100); + mb_Pangenesis_phase[3] = true; + DebugMsg($"=== 黑白塔第三步结束 ===", accessory); + } + + private int getSpecificTowerPlayer(bool hasFactor, bool isTwo, bool isLong, bool isWhiteBuff, bool isFirst) + { + for (int i = 0; i < 8; i++) + { + int idx = isFirst ? i : 7 - i; + bool condition_1 = hasFactor ? mb_Pangenesis_hasFactor[idx] : !mb_Pangenesis_hasFactor[idx]; + bool condition_2 = isTwo ? mb_Pangenesis_isTwo[idx] : !mb_Pangenesis_isTwo[idx]; + bool condition_3 = isLong ? mb_Pangenesis_isLong[idx] : !mb_Pangenesis_isLong[idx]; + bool condition_4 = isWhiteBuff ? mb_Pangenesis_isWhite[idx] : !mb_Pangenesis_isWhite[idx]; + + if (condition_1 && condition_2 && condition_3 && condition_4) + return idx; + } + return -1; + } + + private DrawPropertiesEdit assignDp_TowerDir(Vector3 tower_pos, int player_idx, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = AssignDp.dirPos(tower_pos, delay, destoryAt, $"黑白塔{player_idx}-{tower_pos}", accessory); + return dp; + } + + private void drawPangenesisDir(Vector3 tower_pos, int player_idx, int delay, int destoryAt, ScriptAccessory accessory) + { + var dp = assignDp_TowerDir(tower_pos, player_idx, delay, destoryAt, accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + var dp0 = AssignDp.drawStatic(tower_pos, 0, delay, destoryAt, $"黑白塔{player_idx}", accessory); + dp0.Scale = new(3f); + dp0.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp0); + } + + #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 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 DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + public static uint SourceDataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceDataId"]); + } + + public static uint TargetId(this Event @event) + { + return ParseHexId(@event["TargetId"], out var id) ? id : 0; + } + + public static uint TargetIndex(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetIndex"]); + } + + 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Index(this Event @event) + { + return ParseHexId(@event["Index"], out var id) ? id : 0; + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var id) ? id : 0; + } + + public static uint DirectorId(this Event @event) + { + return ParseHexId(@event["DirectorId"], out var id) ? id : 0; + } + + public static uint Id(this Event @event) + { + return ParseHexId(@event["Id"], 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, uint id) + { + return sa.Data.Objects.SearchByEntityId(id); + } +} + +public static class DirectionCalc +{ + // 以北为0建立list + // InnGame List Dir + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Dir = Pi - InnGame (+ 2pi) + + /// + /// 将游戏基角度(以南为0,逆时针增加)转为逻辑基角度(以北为0,顺时针增加) + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float BaseInnGame2DirRad(float radian) + { + float r = (float)Math.PI - radian; + if (r < 0) r = (float)(r + 2 * Math.PI); + if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float BaseDirRad2InnGame(float radian) + { + float r = (float)Math.PI - radian; + if (r < Math.PI) r = (float)(r + 2 * Math.PI); + if (r > Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 输入逻辑基角度,获取逻辑方位 + /// + /// 逻辑基角度 + /// 方位总数 + /// 逻辑基角度对应的逻辑方位 + public static int DirRadRoundToDirs(float radian, int dirs) + { + var r = Math.Round(radian / (2f / dirs * Math.PI)); + if (r == dirs) r = r - dirs; + return (int)r; + } + + /// + /// 输入坐标,获取正分割逻辑方位(以右上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionFloorToDirs(Vector3 point, Vector3 center, int dirs) + { + // 正分割,0°为分界线,将360°分为dirs份 + var r = Math.Floor(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取斜分割逻辑方位(以正上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionRoundToDirs(Vector3 point, Vector3 center, int dirs) + { + // 斜分割,0° return 0,将360°分为dirs份 + var r = Math.Round(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 将角度转为弧度 + /// + /// 角度值 + /// 对应的弧度值 + public static float angle2Rad(float angle) + { + // 输入角度转为弧度 + float radian = (float)(angle * Math.PI / 180); + return radian; + } + + /// + /// 将弧度转为角度 + /// + /// 弧度值 + /// 对应的角度值 + public static float rad2Angle(float radian) + { + // 输入角度转为弧度 + float angle = (float)(radian / Math.PI * 180); + return angle; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + + // 另一种方案待验证 + // var nextPos = Vector3.Transform((point - center), Matrix4x4.CreateRotationY(radian)) + center; + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(Vector3 center, Vector3 new_point) + { + // 找到某点到中心的弧度 + float radian = MathF.PI - MathF.Atan2(new_point.X - center.X, new_point.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointLR(Vector3 point, int centerx) + { + Vector3 v3 = new(2 * centerx - point.X, point.Y, point.Z); + return v3; + } + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointUD(Vector3 point, int centerz) + { + Vector3 v3 = new(point.X, point.Y, 2 * centerz - point.Z); + return v3; + } +} + +public static class IndexHelper +{ + /// + /// 输入玩家dataid,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int getPlayerIdIndex(uint pid, ScriptAccessory accessory) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int getMyIndex(ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataid,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string getPlayerJobByID(uint pid, ScriptAccessory accessory) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var a = accessory.Data.PartyList.IndexOf(pid); + switch (a) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// + public static string getPlayerJobByIndex(int idx) + { + switch (idx) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } +} + +public static class ColorHelper +{ + public static ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor colorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + public static ScriptColor colorDark = new ScriptColor { V4 = new Vector4(0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorLightBlue = new ScriptColor { V4 = new Vector4(0.48f, 0.40f, 0.93f, 1.0f) }; + public static ScriptColor colorWhite = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; +} + +public static class AssignDp +{ + /// + /// 返回自己指向某目标地点的dp,可修改dp.TargetPosition, dp.Scale + /// + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos(Vector3 target_pos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回起始地点指向某目标地点的dp,可修改dp.Position, dp.TargetPosition, dp.Scale + /// + /// 起始地点 + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos2Pos(Vector3 start_pos, Vector3 target_pos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Position = start_pos; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回自己指向某目标对象的dp,可修改dp.TargetObject, dp.Scale + /// + /// 指向目标对象 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirTarget(uint target_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Owner = accessory.Data.Me; + dp.TargetObject = target_id; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象仇恨或某定点相关的dp,可修改dp.TargetResolvePattern, dp.TargetOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawTargetOrder(uint owner_id, uint order_idx, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.TargetOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象距离相关的dp,可修改dp.CentreResolvePattern, dp.CentreOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCenterOrder(uint owner_id, uint order_idx, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.CentreOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + /// + /// 返回owner与target的dp,可修改 dp.Owner, dp.TargetObject, dp.Scale + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawOwner2Target(uint owner_id, uint target_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetObject = target_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCircle(uint owner_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回环形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawDonut(uint owner_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(22); + dp.InnerScale = new(6); + dp.Radian = float.Pi * 2; + dp.Owner = owner_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 起始位置,通常为场地中心 + /// 旋转角度,以北为0度顺时针 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawStatic(Vector3 center, float rotate_rad, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 20); + dp.Position = center; + dp.Rotation = DirectionCalc.BaseDirRad2InnGame(rotate_rad); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } +} + + +#endregion diff --git a/lianying1997/06-EndWalker/p4s.cs b/lianying1997/06-EndWalker/p4s.cs new file mode 100644 index 0000000..d112db0 --- /dev/null +++ b/lianying1997/06-EndWalker/p4s.cs @@ -0,0 +1,1329 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using Lumina.Excel.Sheets; + +namespace UsamisScript.EndWalker.p4s; + +[ScriptType(name: "P4S [零式万魔殿 边境之狱4]", territorys: [1009], guid: "de9e31e6-d040-48e3-bf0b-aa4e2643f79d", version: "0.0.0.5", author: "Usami", note: noteStr, updateInfo: UpdateInfo)] + +public class p4s +{ + const string noteStr = + """ + 请先按需求检查并设置“用户设置”栏目。 + “逃课”即为黑糖荔枝攻略演示。 + 门神仅线毒提示,本体到二运。 + """; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public static bool DebugMode { get; set; } = false; + public enum Act2StrategyEnum + { + 正攻_Regular, + 逃课_Spread + } + + [UserSetting("二运解法")] + public Act2StrategyEnum Act2Strategy { get; set; } = Act2StrategyEnum.逃课_Spread; + + [UserSetting("站位提示圈绘图-普通颜色")] + public static ScriptColor posColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public static ScriptColor posColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) }; + + public enum P4S_Phase + { + Init, // 初始 + Act1, // 一幕 + Act2, // 二幕 + Act3 // 三幕 + } + public static Vector3 CENTER = new Vector3(100, 0, 100); + P4S_Phase phase = P4S_Phase.Init; + List Drawn = new bool[20].ToList(); // 绘图记录 + int BloodrakeCastTime = 0; // 聚血释放次数 + List BloodrakeNum = [0, 0, 0, 0, 0, 0, 0, 0]; // 聚血次数记录 + int Act1CirclePosition = -1; // 一幕大钢铁方位 + int Act2CirclePosition = -1; // 二幕大钢铁方位 + Act2Solution Act2Sol = new Act2Solution(); // 二幕解决方案 + public void Init(ScriptAccessory accessory) + { + phase = P4S_Phase.Init; + BloodrakeCastTime = 0; // 聚血释放次数 + Drawn = new bool[20].ToList(); // 绘图记录 + accessory.Method.RemoveDraw(".*"); + } + + public static void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + + var str = Act2Sol.Print(); + DebugMsg($"{str}", accessory); + + // drawSpreadDir(Act2Sol, accessory); + } + + [ScriptMethod(name: "门神:聚血初始化(不可控)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27096"], userControl: false)] + public void BloodrakeInit(Event @event, ScriptAccessory accessory) + { + if (BloodrakeCastTime == 0) + BloodrakeNum = [0, 0, 0, 0, 0, 0, 0, 0]; // 聚血次数记录 + BloodrakeCastTime++; + } + + [ScriptMethod(name: "门神:聚血记录(不可控)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:27096"], userControl: false)] + public void BloodrakeRecord(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + + if (BloodrakeCastTime == 1) + BloodrakeNum[tidx] = BloodrakeNum[tidx] + 1; + else if (BloodrakeCastTime == 2) + BloodrakeNum[tidx] = BloodrakeNum[tidx] + 10; + else + return; + } + + [ScriptMethod(name: "门神:线毒行动提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27110"])] + public void DirectorAction(Event @event, ScriptAccessory accessory) + { + var myIndex = accessory.getMyIndex(); + switch (BloodrakeNum[myIndex]) + { + case 0: + accessory.Method.TextInfo($"接毒、接线", 15000, false); + break; + case 1: + accessory.Method.TextInfo($"接毒、不接线", 15000, false); + break; + case 10: + accessory.Method.TextInfo($"不接毒、接线", 15000, false); + break; + case 11: + accessory.Method.TextInfo($"不接毒、不接线", 15000, false); + break; + default: + return; + } + } + + [ScriptMethod(name: "本体:一运,阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27148"], userControl: false)] + public void Act1_PhaseRecord(Event @event, ScriptAccessory accessory) + { + phase = P4S_Phase.Act1; + Act1CirclePosition = -1; // 一幕大钢铁方位 + Drawn[0] = false; + DebugMsg($"当前阶段为:{phase}", accessory); + } + + [ScriptMethod(name: "本体:一运范围提示", eventType: EventTypeEnum.Tether, eventCondition: ["Id:00AD"])] + public void Act1_Field(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act1) return; + if (Act1CirclePosition != -1) return; + if (Drawn[0]) return; + Drawn[0] = true; + + var spos = @event.SourcePosition(); + Act1CirclePosition = spos.PositionRoundToDirs(CENTER, 4); + + DebugMsg($"进行一运绘制{Act1CirclePosition}", accessory); + + var _pos1 = spos; + var _pos2 = spos.RotatePoint(CENTER, float.Pi); + drawBigCircle(_pos1, 0, 11000, $"钢铁1", accessory); + drawBigCircle(_pos2, 0, 11000, $"钢铁2", accessory); + + var _pos3 = spos.RotatePoint(CENTER, float.Pi / 2); + var _pos4 = spos.RotatePoint(CENTER, -float.Pi / 2); + drawBigCircle(_pos3, 14000, 3000, $"钢铁3", accessory); + drawBigCircle(_pos4, 14000, 3000, $"钢铁4", accessory); + } + + private static void drawBigCircle(Vector3 spos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.drawStatic(spos, 0, delay, destoryAt, name); + dp.Scale = new(20); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private static void drawTowerRegion(Vector3 spos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.drawStatic(spos, 0, delay, destoryAt, name); + dp.Scale = new(4); + dp.Color = ColorHelper.colorCyan.V4.WithW(1.5f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "本体:近远死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(2717[45])$"])] + public void MB_TankBusterFarNear(Event @event, ScriptAccessory accessory) + { + var aid = @event.ActionId(); + var sid = @event.SourceId(); + const uint NEAR = 27174; + // const uint FAR = 27175; + var _isNear = aid == NEAR; + + IPlayerCharacter? me = (IPlayerCharacter?)accessory.GetMe(); + if (me == null) return; + var isTank = me.IsTank(); + + var dp1 = accessory.drawCenterOrder(sid, 1, 0, 5000, $"死刑1"); + dp1.CentreResolvePattern = _isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp1.Scale = new(5f); + dp1.Color = isTank ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + + var dp2 = accessory.drawCenterOrder(sid, 2, 0, 5000, $"死刑2"); + dp2.CentreResolvePattern = _isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp2.Scale = new(5f); + dp2.Color = isTank ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + } + + [ScriptMethod(name: "本体:分摊死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28280"])] + public void MB_TankBuster(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var dp = accessory.drawCircle(tid, 0, 5000, $"死刑{tid}"); + dp.Scale = new(6f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "本体:二运,阶段记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:28340"], userControl: false)] + public void Act2_PhaseRecord(Event @event, ScriptAccessory accessory) + { + phase = P4S_Phase.Act2; + Act2CirclePosition = -1; // 二幕大钢铁方位 + for (int i = 1; i <= 5; i++) + Drawn[i] = false; + Act2Sol.Init(); + DebugMsg($"当前阶段为:{phase}", accessory); + } + + [ScriptMethod(name: "本体:二运范围提示", eventType: EventTypeEnum.Tether, eventCondition: ["Id:00AD"])] + public void Act2_Field(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + if (Act2CirclePosition != -1) return; + if (Drawn[1]) return; + Drawn[1] = true; + + var spos = @event.SourcePosition(); + var _tempPos = spos.PositionRoundToDirs(CENTER, 4); + var _atNorthLeftTower = _tempPos == 0 && spos.X < CENTER.X; + var _atEastUpTower = _tempPos == 1 && spos.Z < CENTER.Z; + var _atSouthRightTower = _tempPos == 2 && spos.X > CENTER.X; + var _atWestDownTower = _tempPos == 3 && spos.Z > CENTER.Z; + + if (_atNorthLeftTower || _atEastUpTower || _atSouthRightTower || _atWestDownTower) + return; + + Act2CirclePosition = _tempPos; + DebugMsg($"进行二运绘制{Act2CirclePosition}", accessory); + + var _pos1 = spos; + var _pos2 = spos.RotatePoint(CENTER, float.Pi); + drawBigCircle(_pos1, 0, 19000, $"钢铁1", accessory); + drawBigCircle(_pos2, 0, 19000, $"钢铁2", accessory); + var _pos3 = spos.RotatePoint(CENTER, float.Pi / 2); + var _pos4 = spos.RotatePoint(CENTER, -float.Pi / 2); + drawBigCircle(_pos3, 19000, 7000, $"钢铁3", accessory); + drawBigCircle(_pos4, 19000, 7000, $"钢铁4", accessory); + + Vector3 _towerPos1; + Vector3 _towerPos2; + Vector3 _towerPos3; + Vector3 _towerPos4; + if (Act2CirclePosition % 2 == 0) // 上下钢铁 + { + Act2Sol.isLRSafeFirst = true; + _towerPos1 = _pos1 + (_pos1.Z < CENTER.Z ? new Vector3(-8, 0, 0) : new Vector3(8, 0, 0)); + _towerPos2 = _pos2 + (_pos2.Z < CENTER.Z ? new Vector3(-8, 0, 0) : new Vector3(8, 0, 0)); + _towerPos3 = _pos3 + (_pos3.X < CENTER.X ? new Vector3(0, 0, 8) : new Vector3(0, 0, -8)); + _towerPos4 = _pos4 + (_pos4.X < CENTER.X ? new Vector3(0, 0, 8) : new Vector3(0, 0, -8)); + } + else + { + Act2Sol.isLRSafeFirst = false; + _towerPos1 = _pos1 + (_pos1.X < CENTER.X ? new Vector3(0, 0, 8) : new Vector3(0, 0, -8)); + _towerPos2 = _pos2 + (_pos2.X < CENTER.X ? new Vector3(0, 0, 8) : new Vector3(0, 0, -8)); + _towerPos3 = _pos3 + (_pos3.Z < CENTER.Z ? new Vector3(-8, 0, 0) : new Vector3(8, 0, 0)); + _towerPos4 = _pos4 + (_pos4.Z < CENTER.Z ? new Vector3(-8, 0, 0) : new Vector3(8, 0, 0)); + } + drawTowerRegion(_towerPos3, 0, 19000, $"踩塔3", accessory); + drawTowerRegion(_towerPos4, 0, 19000, $"踩塔4", accessory); + drawTowerRegion(_towerPos1, 19000, 7000, $"踩塔1", accessory); + drawTowerRegion(_towerPos2, 19000, 7000, $"踩塔2", accessory); + } + + [ScriptMethod(name: "本体:二运黄圈引导提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27177"])] + public void Act2_DarkDesign(Event @event, ScriptAccessory accessory) + { + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + var dp = accessory.drawCircle(accessory.Data.PartyList[i], 0, 5000, $"引导黄圈{i}"); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = ColorHelper.colorPink.V4; + dp.Scale = new(6f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + public class Act2Solution + { + public Vector3[] TowerPos = new Vector3[4]; + public Vector3[] CirclePos = new Vector3[4]; + public List FireTargets { get; set; } + public List WindTargets { get; set; } + public List DarkTargets { get; set; } + public bool isLRSafeFirst { get; set; } + public int CircleCastTimes { get; set; } + public Act2Solution() + { + FireTargets = new List { }; + WindTargets = new List { }; + DarkTargets = new List { }; + Init(); + } + public void Init() + { + isLRSafeFirst = false; + FireTargets = new List { }; + WindTargets = new List { }; + DarkTargets = new List { }; + CircleCastTimes = 0; + + TowerPos[0] = new(96, 0, 82); + TowerPos[1] = TowerPos[0].RotatePoint(CENTER, float.Pi / 2); + TowerPos[2] = TowerPos[0].RotatePoint(CENTER, float.Pi); + TowerPos[3] = TowerPos[0].RotatePoint(CENTER, float.Pi / -2); + + CirclePos[0] = new(104, 0, 82); + CirclePos[1] = CirclePos[0].RotatePoint(CENTER, float.Pi / 2); + CirclePos[2] = CirclePos[0].RotatePoint(CENTER, float.Pi); + CirclePos[3] = CirclePos[0].RotatePoint(CENTER, float.Pi / -2); + } + public string Print() + { + string safePosStr = $"{(isLRSafeFirst ? "先左右安全" : "先上下安全")}"; + + string FireTargetsStr = string.Join(", ", FireTargets); + string str1 = $"FireTargets: {FireTargetsStr}"; + + string WindTargetsStr = string.Join(", ", WindTargets); + string str2 = $"WindTargets: {WindTargetsStr}"; + + string DarkTargetsStr = string.Join(", ", DarkTargets); + string str3 = $"DarkTargets: {DarkTargetsStr}"; + + string str = safePosStr + '\n' + str1 + '\n' + str2 + '\n' + str3; + return str; + } + } + + [ScriptMethod(name: "本体:二运,头标记录", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(012[DEF])$"], userControl: false)] + public void Act2_IconRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + var id = @event.Id(); + var tid = @event.TargetId(); + var tidx = accessory.getPlayerIdIndex(tid); + + const uint DARK = 0x012D; + const uint FIRE = 0x012F; + const uint WIND = 0x012E; + + if (id == DARK) + Act2Sol.DarkTargets.Add(tidx); + if (id == FIRE) + Act2Sol.FireTargets.Add(tidx); + if (id == WIND) + Act2Sol.WindTargets.Add(tidx); + } + + [ScriptMethod(name: "本体:二运,连线记录", eventType: EventTypeEnum.Tether, eventCondition: ["Id:00AC"], userControl: false)] + public async void Act2_TetherRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + var sid = @event.SourceId(); + var tid = @event.TargetId(); + var sidx = accessory.getPlayerIdIndex(sid); + var tidx = accessory.getPlayerIdIndex(tid); + + await Task.Delay(100); + + if (Act2Sol.DarkTargets.Contains(sidx) && !Act2Sol.DarkTargets.Contains(tidx)) + Act2Sol.DarkTargets.Add(tidx); + if (Act2Sol.DarkTargets.Contains(tidx) && !Act2Sol.DarkTargets.Contains(sidx)) + Act2Sol.DarkTargets.Add(sidx); + + if (Act2Sol.FireTargets.Contains(sidx) && !Act2Sol.FireTargets.Contains(tidx)) + Act2Sol.FireTargets.Add(tidx); + if (Act2Sol.FireTargets.Contains(tidx) && !Act2Sol.FireTargets.Contains(sidx)) + Act2Sol.FireTargets.Add(sidx); + } + + [ScriptMethod(name: "本体:二运大圈次数记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27150"], userControl: false)] + public void Act2_CircleCastTimeRecord(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + Act2Sol.CircleCastTimes++; + } + + [ScriptMethod(name: "本体:二运就位方案(逃课)", eventType: EventTypeEnum.Tether, eventCondition: ["Id:00AC"])] + public async void Act2_SpreadSolution(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + if (Drawn[2]) return; + Drawn[2] = true; + if (Act2Strategy != Act2StrategyEnum.逃课_Spread) return; + + await Task.Delay(500); + drawSpreadDir(Act2Sol, accessory); + } + + private static void drawSpreadDir(Act2Solution act2sol, ScriptAccessory accessory) + { + bool isNorth = false; + bool isEastDown = false; + bool isEastUp = false; + bool isSouth = false; + bool isWestDown = false; + bool isWestUp = false; + int myIndex = accessory.getMyIndex(); + + if (act2sol.DarkTargets.Contains(myIndex)) + isNorth = true; + if (act2sol.FireTargets.Contains(myIndex)) + { + + if (accessory.Data.MyObject is { } o && o.IsDps()) + { + isWestDown = true; + isEastDown = true; + } + else + { + isWestUp = true; + isEastUp = true; + } + } + if (act2sol.WindTargets.Contains(myIndex)) + isSouth = true; + + // 暗线全上,火线左右,风线全下,只标四方 + var dp = accessory.dirPos2Pos(CENTER, CENTER.ExtendPoint(0, 20), 0, 10000, $"北指路"); + dp.Color = isNorth ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + dp.Scale = new(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + dp = accessory.dirPos2Pos(CENTER, CENTER.ExtendPoint(float.Pi, 20), 0, 10000, $"南指路"); + dp.Color = isSouth ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + dp.Scale = new(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + dp = accessory.dirPos2Pos(CENTER, CENTER.ExtendPoint(float.Pi * 0.42f, 20), 0, 10000, $"东上指路"); + dp.Color = isEastUp ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + dp.Scale = new(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + dp = accessory.dirPos2Pos(CENTER, CENTER.ExtendPoint(float.Pi * -0.42f, 20), 0, 10000, $"西上指路"); + dp.Color = isWestUp ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + dp.Scale = new(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + dp = accessory.dirPos2Pos(CENTER, CENTER.ExtendPoint(float.Pi * 0.58f, 20), 0, 10000, $"东下指路"); + dp.Color = isEastDown ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + dp.Scale = new(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + dp = accessory.dirPos2Pos(CENTER, CENTER.ExtendPoint(float.Pi * -0.58f, 20), 0, 10000, $"西下指路"); + dp.Color = isWestDown ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + dp.Scale = new(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "本体:二运就位方案(正攻)第一步", eventType: EventTypeEnum.Tether, eventCondition: ["Id:00AC"])] + public async void Act2_RegularSolutionFirst(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + if (Drawn[3]) return; + Drawn[3] = true; + if (Act2Strategy != Act2StrategyEnum.正攻_Regular) return; + + await Task.Delay(500); + + var myIndex = accessory.getMyIndex(); + if (Act2Sol.DarkTargets.Contains(myIndex)) + { + // Dark两人在外放圈拉线 + // 等待黄圈被放下(StartCasting黑暗设计圈) + drawDarkTargetRouteFirst(Act2Sol, myIndex, false, accessory); + drawDarkTargetTowerFirst(Act2Sol, myIndex, true, accessory); + } + else + { + // 其他人中间 + var dp = accessory.dirPos(CENTER, 0, 10000, $"二运其他1{myIndex}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + if (Act2Sol.FireTargets.Contains(myIndex)) + drawFireTargetStackFirst(Act2Sol, myIndex, true, accessory); + if (Act2Sol.WindTargets.Contains(myIndex)) + drawWindTargetStackFirst(Act2Sol, myIndex, true, accessory); + } + } + + private static void drawDarkTargetRouteFirst(Act2Solution act2sol, int myIndex, bool isPreparing, ScriptAccessory accessory) + { + Vector3 UPLEFT = CENTER.ExtendPoint((act2sol.isLRSafeFirst ? 45f : -45f).angle2Rad(), 15); + float _rot_radian; + if (accessory.Data.MyObject is { } o && o.IsTank()) + _rot_radian = 0; + else + _rot_radian = float.Pi; + var spos = UPLEFT.RotatePoint(CENTER, _rot_radian); + // 如果是Tank,左上;否则右下 + var dp = accessory.dirPos(spos, 0, 10000, $"二运暗1{isPreparing}{myIndex}"); + dp.Color = isPreparing ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + private static void drawDarkTargetTowerFirst(Act2Solution act2sol, int myIndex, bool isPreparing, ScriptAccessory accessory) + { + int _tower_idx; + if (accessory.Data.MyObject is { } o && o.IsTank()) + _tower_idx = 0; + else + _tower_idx = 2; + if (act2sol.isLRSafeFirst) + _tower_idx++; + + var dp = accessory.dirPos(act2sol.TowerPos[_tower_idx], 0, 10000, $"二运暗塔1{isPreparing}{myIndex}"); + dp.Color = isPreparing ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private static void drawFireTargetStackFirst(Act2Solution act2sol, int myIndex, bool isPreparing, ScriptAccessory accessory) + { + int _circle_idx; + var _myid = accessory.Data.Me; + + if (accessory.Data.MyObject is { } o && !o.IsDps()) + { + if (accessory.Data.MyObject.IsTank()) + _circle_idx = 0; + else + _circle_idx = 2; + } + else + _circle_idx = 0; + + if (act2sol.isLRSafeFirst) + _circle_idx++; + + var dp = accessory.dirPos(act2sol.CirclePos[_circle_idx], 0, 10000, $"二运火分摊1{isPreparing}{myIndex}"); + dp.Color = isPreparing ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private static void drawWindTargetStackFirst(Act2Solution act2sol, int myIndex, bool isPreparing, ScriptAccessory accessory) + { + int _circle_idx = 2; + var _myid = accessory.Data.Me; + + if (act2sol.isLRSafeFirst) + _circle_idx++; + + var dp = accessory.dirPos(act2sol.CirclePos[_circle_idx], 0, 10000, $"二运风分摊1{isPreparing}{myIndex}"); + dp.Color = isPreparing ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "本体:二运就位方案(正攻)第二步", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:27178"])] + public void Act2_RegularSolutionSecond(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + if (Drawn[4]) return; + Drawn[4] = true; + if (Act2Strategy != Act2StrategyEnum.正攻_Regular) return; + + accessory.Method.RemoveDraw($"^(二运火分摊1{true}.*)$"); + accessory.Method.RemoveDraw($"^(二运风分摊1{true}.*)$"); + accessory.Method.RemoveDraw($"^(二运暗1{false}.*)$"); + accessory.Method.RemoveDraw($"^(二运暗塔1{true}.*)$"); + accessory.Method.RemoveDraw($"^(二运其他1.*)$"); + + var myIndex = accessory.getMyIndex(); + if (Act2Sol.DarkTargets.Contains(myIndex)) + { + drawDarkTargetTowerFirst(Act2Sol, myIndex, false, accessory); + // TN分情况踩塔分摊 + drawDarkTargetRouteSecond(Act2Sol, myIndex, true, accessory); + } + else + { + if (Act2Sol.FireTargets.Contains(myIndex)) + { + drawFireTargetStackFirst(Act2Sol, myIndex, false, accessory); + // 四个火分情况 + drawFireTargetRouteSecond(Act2Sol, myIndex, true, accessory); + } + if (Act2Sol.WindTargets.Contains(myIndex)) + { + drawWindTargetStackFirst(Act2Sol, myIndex, false, accessory); + drawWindTargetStackSecond(Act2Sol, myIndex, true, accessory); + } + } + } + private static void drawDarkTargetRouteSecond(Act2Solution act2sol, int myIndex, bool isPreparing, ScriptAccessory accessory) + { + // 暗TH在右汇合,H踩塔T分摊 + int _pos_idx = 1; + if (act2sol.isLRSafeFirst) + _pos_idx++; + + var isTank = accessory.Data.MyObject is { } o && o.IsTank(); + var _target_pos = isTank ? act2sol.CirclePos[_pos_idx] : act2sol.TowerPos[_pos_idx]; + var dp = accessory.dirPos(_target_pos, 0, 10000, $"二运暗2{isPreparing}{myIndex}"); + dp.Color = isPreparing ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private static void drawWindTargetStackSecond(Act2Solution act2sol, int myIndex, bool isPreparing, ScriptAccessory accessory) + { + int _circle_idx = 3; + var _myid = accessory.Data.Me; + + if (act2sol.isLRSafeFirst) + _circle_idx = 0; + + var dp = accessory.dirPos(act2sol.CirclePos[_circle_idx], 0, 10000, $"二运风2{isPreparing}{myIndex}"); + dp.Color = isPreparing ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + private static void drawFireTargetRouteSecond(Act2Solution act2sol, int myIndex, bool isPreparing, ScriptAccessory accessory) + { + int _pos_idx; + + if (accessory.Data.MyObject is { } o && !o.IsDps()) + { + // 火T在右侧分摊 + if (accessory.Data.MyObject.IsTank()) + _pos_idx = 1; + // 火H在左侧踩塔 + else + _pos_idx = 3; + } + else + { + // 优先级最低,右 + if (act2sol.FireTargets.Max() == myIndex) + _pos_idx = 1; + else + _pos_idx = 3; + } + + if (act2sol.isLRSafeFirst) + _pos_idx = _pos_idx == 3 ? 0 : _pos_idx + 1; + + var isHealer = accessory.Data.MyObject is { } o2 && o2.IsHealer(); + var _target_pos = isHealer ? act2sol.TowerPos[_pos_idx] : act2sol.CirclePos[_pos_idx]; + var dp = accessory.dirPos(_target_pos, 0, 10000, $"二运火2{isPreparing}{myIndex}"); + dp.Color = isPreparing ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "本体:二运就位方案(正攻)第三步", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:27150"])] + public void Act2_RegularSolutionThird(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + if (Drawn[5]) return; + Drawn[5] = true; + if (Act2Strategy != Act2StrategyEnum.正攻_Regular) return; + + accessory.Method.RemoveDraw($"^(二运火2{true}.*)$"); + accessory.Method.RemoveDraw($"^(二运风2{true}.*)$"); + accessory.Method.RemoveDraw($"^(二运暗2{true}.*)$"); + accessory.Method.RemoveDraw($"^(二运火分摊1{false}.*)$"); + accessory.Method.RemoveDraw($"^(二运风分摊1{false}.*)$"); + accessory.Method.RemoveDraw($"^(二运暗塔1{false}.*)$"); + + var myIndex = accessory.getMyIndex(); + if (Act2Sol.DarkTargets.Contains(myIndex)) + { + drawDarkTargetRouteSecond(Act2Sol, myIndex, false, accessory); + } + else + { + if (Act2Sol.FireTargets.Contains(myIndex)) + { + drawFireTargetRouteSecond(Act2Sol, myIndex, false, accessory); + } + if (Act2Sol.WindTargets.Contains(myIndex)) + { + drawWindTargetStackSecond(Act2Sol, myIndex, false, accessory); + } + } + } + + [ScriptMethod(name: "本体:二运就位方案删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:27150"], userControl: false)] + public void Act2_RegularSolutionRemove(Event @event, ScriptAccessory accessory) + { + if (phase != P4S_Phase.Act2) return; + if (Act2Sol.CircleCastTimes < 4) return; + accessory.Method.RemoveDraw($".*"); + } +} + +#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 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 TargetId(this Event @event) + { + return ParseHexId(@event["TargetId"], out var id) ? id : 0; + } + + public static uint TargetIndex(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetIndex"]); + } + + 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Index(this Event @event) + { + return ParseHexId(@event["Index"], out var id) ? id : 0; + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var id) ? id : 0; + } + + public static uint DirectorId(this Event @event) + { + return ParseHexId(@event["DirectorId"], out var id) ? id : 0; + } + public static uint Id(this Event @event) + { + return ParseHexId(@event["Id"], 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, uint id) + { + return sa.Data.Objects.SearchByEntityId(id); + } + + public static IGameObject? GetMe(this ScriptAccessory sa) + { + return sa.Data.Objects.LocalPlayer; + } +} + +public static class DirectionCalc +{ + // 以北为0建立list + // InnGame List Dir + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Dir = Pi - InnGame (+ 2pi) + + /// + /// 将游戏基角度(以南为0,逆时针增加)转为逻辑基角度(以北为0,顺时针增加) + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float BaseInnGame2DirRad(this float radian) + { + float r = (float)Math.PI - radian; + if (r < 0) r = (float)(r + 2 * Math.PI); + if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float BaseDirRad2InnGame(this float radian) + { + float r = (float)Math.PI - radian; + if (r < Math.PI) r = (float)(r + 2 * Math.PI); + if (r > Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 输入逻辑基角度,获取逻辑方位 + /// + /// 逻辑基角度 + /// 方位总数 + /// 逻辑基角度对应的逻辑方位 + public static int DirRadRoundToDirs(this float radian, int dirs) + { + var r = Math.Round(radian / (2f / dirs * Math.PI)); + if (r == dirs) r = r - dirs; + return (int)r; + } + + /// + /// 输入坐标,获取正分割逻辑方位(以右上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionFloorToDirs(this Vector3 point, Vector3 center, int dirs) + { + // 正分割,0°为分界线,将360°分为dirs份 + var r = Math.Floor(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取斜分割逻辑方位(以正上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionRoundToDirs(this Vector3 point, Vector3 center, int dirs) + { + // 斜分割,0° return 0,将360°分为dirs份 + var r = Math.Round(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 将角度转为弧度 + /// + /// 角度值 + /// 对应的弧度值 + public static float angle2Rad(this float angle) + { + // 输入角度转为弧度 + float radian = (float)(angle * Math.PI / 180); + return radian; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(this Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + + // TODO 另一种方案待验证 + // var nextPos = Vector3.Transform((point - center), Matrix4x4.CreateRotationY(radian)) + center; + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(this Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(this Vector3 new_point, Vector3 center) + { + // 找到某点到中心的弧度 + float radian = MathF.PI - MathF.Atan2(new_point.X - center.X, new_point.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointLR(this Vector3 point, int centerx) + { + Vector3 v3 = new(2 * centerx - point.X, point.Y, point.Z); + return v3; + } + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointUD(this Vector3 point, int centerz) + { + Vector3 v3 = new(point.X, point.Y, 2 * centerz - point.Z); + return v3; + } +} + +public static class IndexHelper +{ + /// + /// 输入玩家dataid,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int getPlayerIdIndex(this ScriptAccessory accessory, uint pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int getMyIndex(this ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataid,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string getPlayerJobByID(this ScriptAccessory accessory, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var a = accessory.Data.PartyList.IndexOf(pid); + switch (a) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// + /// + public static string getPlayerJobByIndex(this ScriptAccessory accessory, int idx) + { + switch (idx) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } +} + +public static class ColorHelper +{ + public static ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor colorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + public static ScriptColor colorDark = new ScriptColor { V4 = new Vector4(0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorLightBlue = new ScriptColor { V4 = new Vector4(0.48f, 0.40f, 0.93f, 1.0f) }; + public static ScriptColor colorWhite = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; + +} + +public static class AssignDp +{ + /// + /// 返回自己指向某目标地点的dp,可修改dp.TargetPosition, dp.Scale + /// + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos(this ScriptAccessory accessory, Vector3 target_pos, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回起始地点指向某目标地点的dp,可修改dp.Position, dp.TargetPosition, dp.Scale + /// + /// 起始地点 + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos2Pos(this ScriptAccessory accessory, Vector3 start_pos, Vector3 target_pos, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Position = start_pos; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回自己指向某目标对象的dp,可修改dp.TargetObject, dp.Scale + /// + /// 指向目标对象 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirTarget(this ScriptAccessory accessory, uint target_id, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(1f); + dp.Owner = accessory.Data.Me; + dp.TargetObject = target_id; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象仇恨或某定点相关的dp,可修改dp.TargetResolvePattern, dp.TargetOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawTargetOrder(this ScriptAccessory accessory, uint owner_id, uint order_idx, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.TargetOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象距离相关的dp,可修改dp.CentreResolvePattern, dp.CentreOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCenterOrder(this ScriptAccessory accessory, uint owner_id, uint order_idx, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.CentreOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + /// + /// 返回owner与target的dp,可修改 dp.Owner, dp.TargetObject, dp.Scale + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawOwner2Target(this ScriptAccessory accessory, uint owner_id, uint target_id, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetObject = target_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCircle(this ScriptAccessory accessory, uint owner_id, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 起始位置,通常为场地中心 + /// 旋转角度,以北为0度顺时针 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawStatic(this ScriptAccessory accessory, Vector3 center, float radian, int delay, int destoryAt, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 20); + dp.Position = center; + dp.Rotation = DirectionCalc.BaseDirRad2InnGame(radian); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } +} + +#endregion + +#region 测试区 +// -------------------------------------- +public static class ClassTest +{ + public static int Counting = 0; + + public static void classTestMethod(Event @event, ScriptAccessory accessory) + { + accessory.Method.SendChat($"/e counting: {Counting++}"); + } +} + +// 事件调用 +// [ScriptMethod(name: "Test Class", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:133"])] +// public void StartCasting(Event @event, ScriptAccessory accessory) => ClassTest.classTestMethod(@event, accessory); + +// List内数据打印 +// string rotateDirStr = string.Join(", ", RotateCircleDir); +// DebugMsg(rotateDirStr, accessory); + +// CLASS +public class Blade +{ + public UInt32 Id { get; set; } + public double X { get; set; } + public double Y { get; set; } + public double Rotation { get; set; } + public Blade(UInt32 id, double x, double y, double rotation) + { + Id = id; + X = x; + Y = y; + Rotation = rotation; + } +} + +// ConcurrentBag 和 List 的区别: +// Thread Safety: ConcurrentBag is designed to be used in multi-threaded scenarios. +// It allows multiple threads to add and remove items concurrently + +// private ConcurrentBag blades = new ConcurrentBag(); +// blades.Add(new Blade( +// id: Convert.ToUInt32(@event["SourceId"], 16), +// x: Convert.ToDouble(pos.X), +// y: Convert.ToDouble(pos.Z), +// rotation: Convert.ToDouble(@event["SourceRotation"]) +// )); + +#endregion diff --git a/lianying1997/06-EndWalker/p8s.cs b/lianying1997/06-EndWalker/p8s.cs new file mode 100644 index 0000000..cea5999 --- /dev/null +++ b/lianying1997/06-EndWalker/p8s.cs @@ -0,0 +1,2360 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Network.Structures.InfoProxy; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Module.Draw.Manager; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs; +using KodakkuAssist.Module.Script.Type; + +namespace UsamisScript.EndWalker.p8s; + +[ScriptType(name: "P8S [零式万魔殿 炼净之狱4]", territorys: [1088], + guid: "97df6974-c726-4a00-9016-293c184adf5c", version: "0.0.0.7", author: "Usami", note: noteStr, updateInfo: UpdateInfo)] +public class p8s +{ + const string noteStr = + """ + v0.0.0.7 + 一车设置场中H2,正右ST。 + 鸭门。 + """; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + [UserSetting("Debug模式,非开发用请关闭")] + public static bool DebugMode { get; set; } = false; + + [UserSetting("启用本体一运塔[小队频道]发宏")] + public static bool HC1_ChatGuidance { get; set; } = false; + + [UserSetting("站位提示圈绘图-普通颜色")] + public static ScriptColor posColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public static ScriptColor posColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) }; + + // 门神 记录分摊分散 + bool db_isStack; + // 门神 蓝线灼炎次数 + int db_torchFlameNum; + int db_illusorySunforgeTimes; + int db_gorgonIdx; + int db_gorgonPartnerIdx; + + uint db_gorgonTarget; + int db_gorgonTargetPos; + + List db_upliftOrder = []; + List db_flareTarget = []; + bool[] db_isFirstRound = [false, false, false, false, false, false, false, false]; + bool[] db_isGorgonEye = [false, false, false, false, false, false, false, false]; + List db_GorgonPosition = []; + List db_GorgonSid = []; + + public enum MB_Phase + { + Opening, + NA1, + HC1, + LD, + NA2, + HC2, + } + MB_Phase mb_phase = MB_Phase.Opening; + static bool mb_isLeftCleave = false; // 本体,是左半场刀 + // 本体,是紫圈目标 + bool[] mb_isNATarget = [false, false, false, false, false, false, false, false]; + + // 本体一运对应BUFF目标 + // 2人分摊,3人分摊,短alpha,长alpha,短beta,长beta,短gamma,长gamma + List mb_hc1_sid = [0, 0, 0, 0, 0, 0, 0, 0]; + uint mb_sideCleaveNum; + uint mb_conceptFinNum; + // string? mb_towerColor; + string? mb_mentionTxt; + Vector3 mb_TwoStackDestination = default; + Vector3 mb_ThreeStackDestination = default; + Vector3 mb_UnmergeDestination = default; + bool[] mb_joinMerge = [false, false, false]; + + uint mb_alphaLongFollower; + uint mb_betaLongFollower; + uint mb_gammaLongFollower; + bool mb_NA1_isTNFixed = false; + bool mb_NA1_isLine1Safe = false; + List mb_LD_playerOrder = []; // 本体万象阶段,玩家易伤顺序 + List mb_LD_towerOrder = []; // 本体万象阶段,塔顺序 + + public void Init(ScriptAccessory accessory) + { + db_torchFlameNum = 0; + db_illusorySunforgeTimes = 0; + db_gorgonIdx = 0; + db_gorgonPartnerIdx = -1; + db_isStack = false; + db_gorgonTarget = 0; + db_gorgonTargetPos = 0; + bool[] db_isFirstRound = [false, false, false, false, false, false, false, false]; + bool[] db_isGorgonEye = [false, false, false, false, false, false, false, false]; + + db_upliftOrder = []; + db_flareTarget = []; + db_GorgonPosition = []; + db_GorgonSid = []; + + mb_phase = MB_Phase.Opening; + + mb_isLeftCleave = false; // 本体,是左半场刀 + mb_isNATarget = [false, false, false, false, false, false, false, false]; + + mb_hc1_sid = [0, 0, 0, 0, 0, 0, 0, 0]; + mb_sideCleaveNum = 0; + mb_conceptFinNum = 0; + mb_mentionTxt = "Hello Koda!"; + + mb_TwoStackDestination = default; + mb_ThreeStackDestination = default; + mb_UnmergeDestination = default; + mb_joinMerge = [false, false, false]; + + mb_alphaLongFollower = 0; + mb_betaLongFollower = 0; + mb_gammaLongFollower = 0; + mb_NA1_isTNFixed = false; + mb_NA1_isLine1Safe = false; + + mb_LD_playerOrder = []; // 本体万象阶段,玩家易伤顺序 + mb_LD_towerOrder = []; // 本体万象阶段,塔顺序 + + accessory.Method.RemoveDraw(".*"); + } + public static void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + var msg = @event["Message"].ToString(); + accessory.Method.SendChat($"/e 获得玩家发送的消息:{msg}"); + + mb_mentionTxt = "Line1"; + accessory.Method.SendChat($"/e {mb_mentionTxt}"); + mb_mentionTxt = "Line2"; + accessory.Method.SendChat($"/e {mb_mentionTxt}"); + mb_mentionTxt = "Line3"; + accessory.Method.SendChat($"/e {mb_mentionTxt}"); + + } + + [ScriptMethod(name: "防击退删除画图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(7559|7548|7389)$"], userControl: false)] + // 沉稳咏唱|亲疏自行|原初的解放 + public void RemoveLine(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + if (sid == accessory.Data.Me) + { + accessory.Method.RemoveDraw("^(可防击退-.*)$"); + // DebugMsg($"/e 检测到防击退,删除击退标志", accessory); + } + } + + #region 门神:基础 + + [ScriptMethod(name: "门神:记录分摊分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3099[67])$"], userControl: false)] + public void DB_RecordSpreadAndStack(Event @event, ScriptAccessory accessory) + { + // 30996 分散(八分核爆之念) + // 30997 分摊(四分核爆之念) + db_isStack = @event.ActionId() == 30996; + DebugMsg($"已记录下【{(db_isStack ? "分散" : "分摊")}】。", accessory); + } + + [ScriptMethod(name: "门神:蓝线范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31015"])] + public void DB_TorchFlame(Event @event, ScriptAccessory accessory) + { + // if (db_torchFlameNum >= 12) return; + // accessory.Method.SendChat($"/e db_torchFlameNum {db_torchFlameNum}……"); + + db_torchFlameNum++; + var spos = @event.SourcePosition(); + var dp = assignDp_TorchFlame(spos, 0, 10000, $"灼炎{db_torchFlameNum}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + private static DrawPropertiesEdit assignDp_TorchFlame(Vector3 pos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(10, 10); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = pos - new Vector3(0, 0, 5); + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + [ScriptMethod(name: "门神:龙龙凤凤", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3099[45])$"])] + public void DB_Sunforge(Event @event, ScriptAccessory accessory) + { + var epos = @event.EffectPosition(); + var srot = @event.SourceRotation(); + // var epos = JsonConvert.DeserializeObject(@event["EffectPosition"]); + // var tpos = JsonConvert.DeserializeObject(@event["TargetPosition"]); + // var srot = JsonConvert.DeserializeObject(@event["SourceRotation"]); + // var isHotTail = @event["ActionId"] == "30994"; + + var isDragon = @event.ActionId() == 30994; + var isOpening = db_torchFlameNum == 12; + + // 开场会先出蓝火,后出龙凤,因此使用两种颜色区分。 + if (isDragon) + { + var dp = assignDp_DragonLine(epos, srot, 0, 7700, $"龙龙{db_torchFlameNum}", accessory); + dp.Delay = isOpening ? 3000 : 0; + dp.DestoryAt = isOpening ? 4700 : 7700; + dp.Color = isOpening ? ColorHelper.DelayDangerColor.V4 : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + else + { + var dp = assignDp_PhoenixWing(epos, srot, 0, 7700, $"凤凤{db_torchFlameNum}", accessory); + dp.Color = isOpening ? ColorHelper.DelayDangerColor.V4 : accessory.Data.DefaultDangerColor; + dp.Delay = isOpening ? 3000 : 0; + dp.DestoryAt = isOpening ? 4700 : 7700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + accessory.Method.TextInfo($"即将【{(db_isStack ? "分散" : "分摊")}】……", 8000, true); + } + + private static DrawPropertiesEdit assignDp_DragonLine(Vector3 pos, float rot, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(14, 45); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Rotation = rot; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + private static DrawPropertiesEdit assignDp_PhoenixWing(Vector3 pos, float rot, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(45, 20); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Position = pos; + dp.Rotation = rot; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + [ScriptMethod(name: "门神:死刑", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31045"])] + public void DB_TankBuster(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var tid = @event.TargetId(); + // if (!ParseObjectId(@event["SourceId"], out var sid)) return; + // if (!ParseObjectId(@event["TargetId"], out var tid)) return; + + var dp1 = assignDp_TankBusterLine(sid, 0, 6000, $"直线死刑-目标", accessory); + dp1.TargetObject = tid; + + var dp2 = assignDp_TankBusterLine(sid, 0, 9000, $"直线死刑-一仇", accessory); + dp2.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp1); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp2); + } + + private static DrawPropertiesEdit assignDp_TankBusterLine(uint sid, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Owner = sid; + dp.Scale = new(5, 40); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + [ScriptMethod(name: "门神:变身", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3105[12])$"])] + public void DB_Reforge(Event @event, ScriptAccessory accessory) + { + var isSnake = @event.ActionId() == 31052; + var sid = @event.SourceId(); + + if (isSnake) + { + var dp = assignDp_ReforgeSnakeCircle(sid, 0, 11500, $"蛇蛇钢铁", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + else + { + var dp = assignDp_ReforgeBeastKB(sid, 4000, 6000, $"车车击退", accessory); + dp.Color = accessory.Data.DefaultDangerColor.WithW(3); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + } + private static DrawPropertiesEdit assignDp_ReforgeSnakeCircle(uint sid, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(10); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + private static DrawPropertiesEdit assignDp_ReforgeBeastKB(uint sid, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(2, 20); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = accessory.Data.Me; + dp.Rotation = float.Pi; + dp.TargetObject = sid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + #endregion + + #region 门神:一车 + + [ScriptMethod(name: "门神:一车分散提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31027"])] + public void DB_BeastSpread(Event @event, ScriptAccessory accessory) + { + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + var dp = AssignDp.drawCircle(accessory.Data.PartyList[i], 0, 13000, $"一车分散告警{i}", accessory); + dp.Scale = new(5); + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.6f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + drawSpreadPos(accessory); + } + + private static void drawSpreadPos(ScriptAccessory accessory) + { + Vector3[] safePos = new Vector3[8]; + safePos[0] = new Vector3(100, 0, 90); + safePos[1] = new Vector3(110, 0, 100); + safePos[2] = new Vector3(90, 0, 100); + safePos[3] = new Vector3(100, 0, 100); + safePos[4] = new Vector3(90, 0, 110); + safePos[5] = new Vector3(110, 0, 110); + safePos[6] = new Vector3(90, 0, 90); + safePos[7] = new Vector3(110, 0, 90); + + var myIndex = IndexHelper.getMyIndex(accessory); + + for (int i = 0; i < 8; i++) + { + var dp = AssignDp.drawStatic(safePos[i], 0, 0, 6000, $"一车起始定点{i}", accessory); + dp.Scale = new(1.5f); + dp.Color = myIndex == i ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + var dp0 = AssignDp.dirPos(safePos[myIndex], 0, 6000, $"一车起始指路{myIndex}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + + // 决定受击顺序 + [ScriptMethod(name: "门神:一车受击顺序记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31029"], userControl: false)] + public void DB_UpliftRecord(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + if (@event.TargetIndex() != 1) return; + db_upliftOrder.Add(tid); + } + + // 决定引导顺序 + [ScriptMethod(name: "门神:一车引导", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31030"])] + public void DB_Stomp(Event @event, ScriptAccessory accessory) + { + if (db_upliftOrder.Count(x => x == accessory.Data.Me) > 1) + { + accessory.Method.TextInfo("自求多福吧……", 8000, true); + return; + } + var myTurn = db_upliftOrder.IndexOf(accessory.Data.Me) / 2 + 1; + drawBeastStompRouteDir(myTurn, accessory); + switch (myTurn) + { + case 1: accessory.Method.TextInfo("第一轮,先【左上1点引导】,后【A/D躲避】", 8000, true); return; + case 2: accessory.Method.TextInfo("第二轮,先【场中引导】,后【A/D躲避】", 8000, true); return; + case 3: accessory.Method.TextInfo("第三轮,先【A/D躲避】,后【左上1点引导】", 8000, true); return; + case 4: accessory.Method.TextInfo("第四轮,先【A/D躲避】,后【场中引导】", 8000, true); return; + default: accessory.Method.TextInfo("似乎赫淮斯托斯没有注意到你……", 8000, true); return; + } + } + private static void drawBeastStompRouteDir(int myTurn, ScriptAccessory accessory) + { + // 左上,正上,正左,中 + Vector3[] beastStompPos = [new(90, 0, 90), new(100, 0, 90), new(90, 0, 100), new(100, 0, 100)]; + for (int i = 0; i < 4; i++) + { + var dp = AssignDp.drawStatic(beastStompPos[i], 0, 0, 8000, $"一车位置{i}", accessory); + dp.Scale = new(1.5f); + dp.Color = posColorNormal.V4.WithW(0.6f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + List stompIdx = myTurn switch + { + 1 => new List { 0, 1, 1, 1 }, + 2 => new List { 3, 3, 2, 2 }, + 3 => new List { 1, 0, 0, 1 }, + 4 => new List { 1, 1, 3, 3 }, + _ => new List { -1, -1, -1, -1 } + }; + + List delayTime = [0, 5250, 7750, 10250]; + List destoryTime = [5250, 2500, 2500, 2500]; + + for (int i = 0; i < 4; i++) + { + var dp_a = AssignDp.dirPos(beastStompPos[stompIdx[i]], delayTime[i], destoryTime[i], $"指路{i}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp_a); + + if (i + 1 == 4) break; + var dp_b = AssignDp.dirPos2Pos(beastStompPos[stompIdx[i]], beastStompPos[stompIdx[i + 1]], delayTime[i], destoryTime[i], $"准备就位{i}", accessory); + dp_b.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp_b); + } + } + + #endregion + + #region 门神:一蛇 + + // 177.3 "Gorgomanteia" Ability { id: "791A", source: "Hephaistos" } + // 记录玩家buff + // 3004 BBC 麻将1 + // 3005 BBD 麻将2 + // 3351 D17 石化 + // 3326 CFE 放毒 + [ScriptMethod(name: "门神:一蛇记录Buff", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3004|3005|3351|3326)$"], userControl: false)] + public void DB_BRC_Gorgomanteia(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var index = IndexHelper.getPlayerIdIndex(tid, accessory); + if (index == -1) return; + + if (@event.StatusID() == 3004 || @event.StatusID() == 3005) + db_isFirstRound[index] = @event.StatusID() == 3004; + else + db_isGorgonEye[index] = @event.StatusID() == 3351; + + } + + // 可能存在一蛇BUFF时掉人,躺着的没有BUFF…… + [ScriptMethod(name: "门神:一蛇找队友", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31018"], userControl: false)] + public void DB_GorgonPartnerRecord(Event @event, ScriptAccessory accessory) + { + var MyIndex = IndexHelper.getMyIndex(accessory); + for (int i = 0; i < 8; i++) + { + // 在8个玩家中,寻找:与自己同一轮,与自己拥有同类型buff的玩家,且不能是自己 + bool isSameRound = db_isFirstRound[i] == db_isFirstRound[MyIndex]; + bool isSameGorgonEye = db_isGorgonEye[i] == db_isGorgonEye[MyIndex]; + bool isNotMyself = i != MyIndex; + + if (isSameRound && isSameGorgonEye && isNotMyself) + { + db_gorgonPartnerIdx = i; + DebugMsg($"找到你的搭档: {IndexHelper.getPlayerJobByIndex(i)}", accessory); + break; // 找到一个符合条件的队友,退出循环 + } + } + } + + [ScriptMethod(name: "门神:一蛇记录位置", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31019"], userControl: false)] + public void DB_GorgonPositionRecord(Event @event, ScriptAccessory accessory) + { + var spos = @event.SourcePosition(); + var sid = @event.SourceId(); + // db_GorgonPosition与db_GorgonSid两个list,同一个index记录下同一蛇的SourceID与八向逻辑方位 + db_GorgonPosition.Add(DirectionCalc.PositionRoundToDirs(spos, new(100, 0, 100), 8)); + db_GorgonSid.Add(sid); + } + + [ScriptMethod(name: "门神:一蛇视线范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31019"])] + public void DB_Petrifaction(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var spos = @event.SourcePosition(); + + // 绘制蛇蛇出现地点 + var dp1 = assignDp_SnakeAppearPos(spos, 0, 10000, $"蛇蛇{sid}出现地点", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + + // 绘制背对蛇蛇 + var dp2 = assignDp_SnakeSightAvoid(spos, 0, 10000, $"蛇蛇{sid}背对", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.SightAvoid, dp2); + } + + private static DrawPropertiesEdit assignDp_SnakeAppearPos(Vector3 pos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(2); + dp.Color = ColorHelper.GorgonColor.V4; + dp.Position = pos; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + private static DrawPropertiesEdit assignDp_SnakeSightAvoid(Vector3 tpos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = accessory.Data.Me; + dp.TargetPosition = tpos; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + [ScriptMethod(name: "门神:一蛇优先级记录", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31019"], userControl: false)] + public void DB_GorgonPriority(Event @event, ScriptAccessory accessory) + { + lock (this) + { + // 蛇释放石化后计数 + db_gorgonIdx++; + var myIndex = IndexHelper.getMyIndex(accessory); + if (db_gorgonIdx != 2 && db_gorgonIdx != 4) return; + if (db_gorgonIdx == 2 && !db_isFirstRound[myIndex]) return; + if (db_gorgonIdx == 4 && db_isFirstRound[myIndex]) return; + + // 寻找目标蛇 + // 分辨与搭档的优先级 + bool isHighPriority = myIndex < db_gorgonPartnerIdx ? true : false; + DebugMsg($"我的优先级是【{(isHighPriority ? "高" : "低")}】", accessory); + + // accessory.Method.SendChat($"/e 我的优先级是{(isHighPriority ? "高" : "低")}……"); + uint gorgon_HighPriority; + uint gorgon_LowPriority; + int gorgon_HighPriorityPosition; + int gorgon_LowPriorityPosition; + + // 设置蛇位置优先级 + if (db_isFirstRound[myIndex]) + { + gorgon_HighPriorityPosition = db_GorgonPosition[0] < db_GorgonPosition[1] ? db_GorgonPosition[0] : db_GorgonPosition[1]; + gorgon_LowPriorityPosition = db_GorgonPosition[0] == gorgon_HighPriorityPosition ? db_GorgonPosition[1] : db_GorgonPosition[0]; + gorgon_HighPriority = db_GorgonPosition[0] < db_GorgonPosition[1] ? db_GorgonSid[0] : db_GorgonSid[1]; + gorgon_LowPriority = db_GorgonSid[0] == gorgon_HighPriority ? db_GorgonSid[1] : db_GorgonSid[0]; + } + else + { + gorgon_HighPriorityPosition = db_GorgonPosition[2] < db_GorgonPosition[3] ? db_GorgonPosition[2] : db_GorgonPosition[3]; + gorgon_LowPriorityPosition = db_GorgonPosition[2] == gorgon_HighPriorityPosition ? db_GorgonPosition[3] : db_GorgonPosition[2]; + gorgon_HighPriority = db_GorgonPosition[2] < db_GorgonPosition[3] ? db_GorgonSid[2] : db_GorgonSid[3]; + gorgon_LowPriority = db_GorgonSid[2] == gorgon_HighPriority ? db_GorgonSid[3] : db_GorgonSid[2]; + } + + DebugMsg($"高优先级蛇在{gorgon_HighPriorityPosition},低优先级蛇在{gorgon_LowPriorityPosition}", accessory); + + db_gorgonTarget = isHighPriority ? gorgon_HighPriority : gorgon_LowPriority; + db_gorgonTargetPos = isHighPriority ? gorgon_HighPriorityPosition : gorgon_LowPriorityPosition; + } + } + + // 201.1 "Eye of the Gorgon 1" Ability { id: "792D", source: "Hephaistos" } + // 玩家放蛇范围提示 + [ScriptMethod(name: "门神:一蛇石化眼指向", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31019"])] + public void DB_EyeGorgon(Event @event, ScriptAccessory accessory) + { + Task.Delay(50).ContinueWith(t => + { + var myIndex = IndexHelper.getMyIndex(accessory); + + if (!db_isGorgonEye[myIndex]) return; + if (db_gorgonIdx != 2 && db_gorgonIdx != 4) return; + if (db_gorgonIdx == 2 && !db_isFirstRound[myIndex]) return; + if (db_gorgonIdx == 4 && db_isFirstRound[myIndex]) return; + + // 绘制石化眼扇形 + var dp1 = assignDp_GorgonEyeFan(0, 5000, $"石化眼{db_gorgonIdx}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp1); + + // 绘制石化眼目标蛇蛇 + var dp2 = assignDp_SnakeTarget(db_gorgonTarget, 0, 10000, $"蛇蛇目标{db_gorgonIdx}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + + var dp3 = AssignDp.dirTarget(db_gorgonTarget, 0, 5000, $"指向目标蛇蛇{db_gorgonIdx}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + + // 传输信息 + string gorgon_target_position_txt = "未知位置"; // 默认值 + switch (db_gorgonTargetPos) + { + case 0: gorgon_target_position_txt = "正上方 A"; break; + case 1: gorgon_target_position_txt = "右上方 2"; break; + case 2: gorgon_target_position_txt = "正右方 B"; break; + case 3: gorgon_target_position_txt = "右下方 3"; break; + case 4: gorgon_target_position_txt = "正下方 C"; break; + case 5: gorgon_target_position_txt = "左下方 4"; break; + case 6: gorgon_target_position_txt = "正左方 D"; break; + case 7: gorgon_target_position_txt = "左上方 1"; break; + } + accessory.Method.TextInfo($"控制住【{gorgon_target_position_txt}】点蛇的行动……", 8000, true); + }); + } + private static DrawPropertiesEdit assignDp_GorgonEyeFan(int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(50); + dp.Radian = float.Pi / 4; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = accessory.Data.Me; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + private static DrawPropertiesEdit assignDp_SnakeTarget(uint tid, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(2); + dp.Color = accessory.Data.DefaultSafeColor; + dp.Owner = tid; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + // 204.2 "Blood of the Gorgon 1" Ability { id: "792F", source: "Hephaistos" } + // 玩家放毒范围提示 + [ScriptMethod(name: "门神:一蛇放毒指向", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:31019"])] + public void DB_BloodGorgon(Event @event, ScriptAccessory accessory) + { + Task.Delay(50).ContinueWith(t => + { + var myIndex = IndexHelper.getMyIndex(accessory); + + if (db_isGorgonEye[myIndex]) return; + if (db_gorgonIdx != 2 && db_gorgonIdx != 4) return; + if (db_gorgonIdx == 2 && !db_isFirstRound[myIndex]) return; + if (db_gorgonIdx == 4 && db_isFirstRound[myIndex]) return; + + // 绘制放毒圆形 + var dp1 = assignDp_PoisonCircle(accessory.Data.Me, 0, 5000, $"放毒圆形{db_gorgonIdx}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp1); + + // 绘制放毒目标蛇蛇 + var dp2 = assignDp_SnakeTarget(db_gorgonTarget, 0, 10000, $"蛇蛇目标{db_gorgonIdx}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp2); + + var dp3 = AssignDp.dirTarget(db_gorgonTarget, 0, 10000, $"指向目标蛇蛇{db_gorgonIdx}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + + // 传输信息 + string gorgon_target_position_txt = "未知位置"; // 默认值 + switch (db_gorgonTargetPos) + { + case 0: gorgon_target_position_txt = "正上方 A"; break; + case 1: gorgon_target_position_txt = "右上方 2"; break; + case 2: gorgon_target_position_txt = "正右方 B"; break; + case 3: gorgon_target_position_txt = "右下方 3"; break; + case 4: gorgon_target_position_txt = "正下方 C"; break; + case 5: gorgon_target_position_txt = "左下方 4"; break; + case 6: gorgon_target_position_txt = "正左方 D"; break; + case 7: gorgon_target_position_txt = "左上方 1"; break; + } + accessory.Method.TextInfo($"用毒素制止【{gorgon_target_position_txt}】点蛇的行动……", 8000, true); + }); + } + + private static DrawPropertiesEdit assignDp_PoisonCircle(uint owner_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(4); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = owner_id; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + return dp; + } + + #endregion + + #region 门神:一分身 + + [ScriptMethod(name: "门神:幻影龙龙凤凤", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3105[89])$"])] + public void DB_IllusorySunforge(Event @event, ScriptAccessory accessory) + { + db_illusorySunforgeTimes++; + var epos = @event.EffectPosition(); + var srot = @event.SourceRotation(); + + var isDragon = @event.ActionId() == 31058; + + if (isDragon) + { + var dp = assignDp_DragonLine(epos, srot, 0, 7700, $"龙龙分身", accessory); + dp.Color = ColorHelper.DelayDangerColor.V4; + dp.Delay = db_illusorySunforgeTimes < 3 ? 0 : 1000; + dp.DestoryAt = 7500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + else + { + var dp = assignDp_PhoenixWing(epos, srot, 0, 7700, $"凤凤分身", accessory); + dp.Scale = new(90, 20); + dp.Color = ColorHelper.DelayDangerColor.V4; + dp.Delay = db_illusorySunforgeTimes < 3 ? 0 : 1000; + dp.DestoryAt = 7500; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + + // 龙龙凤凤与分散 + [ScriptMethod(name: "门神:一分身分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31009"])] + public void DB_ManifoldFlames(Event @event, ScriptAccessory accessory) + { + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + var dp = assignDp_IllusionSpread(accessory.Data.PartyList[i], 0, 6500, $"一分身分散{i}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + private static DrawPropertiesEdit assignDp_IllusionSpread(uint sid, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + [ScriptMethod(name: "门神:一分身易伤收集", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:29390"], userControl: false)] + public void DB_HemitheosFlare(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + db_flareTarget.Add(tid); + } + + [ScriptMethod(name: "门神:一分身引导", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31009"])] + public void DB_NestFlamevipers(Event @event, ScriptAccessory accessory) + { + Task.Delay(6000).ContinueWith(t => + { + var hasDebuff = db_flareTarget.Contains(accessory.Data.Me); + var sid = @event.SourceId(); + + for (uint i = 1; i < 5; i++) + { + var dp = AssignDp.drawTargetOrder(sid, i, 0, 4000, $"一分身引导-{i}", accessory); + dp.Scale = new(5, 40); + dp.TargetOrderIndex = i; + dp.Color = hasDebuff ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + if (hasDebuff) + accessory.Method.TextInfo("正点远离避开引导", 4000, true); + else + accessory.Method.TextInfo("斜点靠近引导", 4000, true); + }); + } + + [ScriptMethod(name: "门神:一分身分摊分散", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3100[67])$"])] + public void DB_EmergentFlare(Event @event, ScriptAccessory accessory) + { + var isSpread = @event.ActionId() == 31007; + var sid = @event.SourceId(); + if (isSpread) + { + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + var dp = AssignDp.drawOwner2Target(sid, accessory.Data.PartyList[i], 0, 6000, $"一分身分散直线{i}", accessory); + dp.Scale = new(5, 40); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + else + { + int[] parterner = [6, 7, 4, 5, 2, 3, 0, 1]; + var myIndex = IndexHelper.getMyIndex(accessory); + for (int i = 0; i < 4; i++) + { + var ii = myIndex > 3 ? i + 4 : i; + var dp = AssignDp.drawCircle(accessory.Data.PartyList[ii], 0, 6000, $"一分身分摊{ii}", accessory); + dp.Scale = new(5); + dp.Color = myIndex == ii || myIndex == parterner[ii] ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + } + #endregion + + + #region 本体:基础 + + [ScriptMethod(name: "本体:半场刀", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3119[12])$"])] + public void MB_SideCleave(Event @event, ScriptAccessory accessory) + { + var isleft = @event.ActionId() == 31191; + mb_isLeftCleave = isleft; + var dp = AssignDp.drawStatic(new Vector3(100, 0, 100), float.Pi, 0, 5700, $"本体半场刀{isleft}", accessory); + dp.Scale = new(20, 40); + dp.Position = isleft ? new(90, 0, 80) : new(110, 0, 80); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "本体:半场刀增加阶段", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3119[12])$"], userControl: false)] + public void MB_PhaseAdd(Event @event, ScriptAccessory accessory) + { + mb_sideCleaveNum++; + } + + [ScriptMethod(name: "本体:阶段(术式记录)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31163"], userControl: false)] + public void MB_PhaseChange_NA(Event @event, ScriptAccessory accessory) + { + mb_phase = mb_phase switch + { + MB_Phase.Opening => MB_Phase.NA1, + MB_Phase.NA1 => MB_Phase.NA2, + _ => MB_Phase.NA1 + }; + } + + #endregion + + #region 本体:一术士 + + [ScriptMethod(name: "本体:一紫圈记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2552"], userControl: false)] + public void MB_NaturalAlignment(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.NA1) return; + var tid = @event.TargetId(); + var tidx = accessory.Data.PartyList.IndexOf(tid); + if (tidx == -1) return; + mb_isNATarget[tidx] = true; + + if (mb_isNATarget.Count(x => x == true) != 2) return; + // 若紫圈目标为DPS,则TN固定站位 + mb_NA1_isTNFixed = tidx > 3; + + } + + [ScriptMethod(name: "本体:黄圈引导", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31369"])] + public void MB_TyrantFlare(Event @event, ScriptAccessory accessory) + { + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + var dp = AssignDp.drawCircle(accessory.Data.PartyList[i], 0, 3000, $"黄圈引导{i}", accessory); + dp.Scale = new(6); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.6f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + // 强制咏唱,出现分摊分散/冰火顺序图案 + [ScriptMethod(name: "本体:一分摊分散", eventType: EventTypeEnum.StatusAdd, eventCondition: ["Param:regex:^(48[02])$"])] + public void MB_ForceStackSpread(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.NA1) return; + var isStackFirst = @event.Param() == 480; + var myIndex = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + if (mb_isNATarget[myIndex]) + accessory.Method.TextInfo($"先【{(isStackFirst ? "避开分摊" : "分散")}】,后【{(isStackFirst ? "分散" : "避开分摊")}】", 5000, true); + else + accessory.Method.TextInfo($"先【{(isStackFirst ? "分摊" : "分散")}】,后【{(isStackFirst ? "分散" : "分摊")}】", 5000, true); + + // 绘制分散 + drawForceSpread(isStackFirst, mb_isNATarget, accessory); + drawSpreadDir(isStackFirst, myIndex, accessory); + // 绘制分摊 + drawForceStack(isStackFirst, myIndex, mb_isNATarget[myIndex], accessory); + } + private static void drawForceSpread(bool isStackFirst, bool[] naTargetList, ScriptAccessory accessory) + { + var spreadTime = isStackFirst ? 6100 : 3000; + + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + if (naTargetList[i]) continue; + var dp = AssignDp.drawCircle(accessory.Data.PartyList[i], spreadTime, spreadTime, $"一紫圈分散{i}", accessory); + dp.Scale = new(6); + dp.ScaleMode = ScaleMode.ByTime; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + private static void drawForceStack(bool isStackFirst, int myIndex, bool isTarget, ScriptAccessory accessory) + { + var stackTime = isStackFirst ? 3000 : 6100; + var stackOwner = myIndex < 4 ? accessory.Data.PartyList[myIndex + 4] : accessory.Data.PartyList[myIndex - 4]; + var owner_id = isTarget ? stackOwner : accessory.Data.Me; + var dp = AssignDp.drawCircle(owner_id, stackTime, stackTime, $"一紫圈分摊{myIndex}", accessory); + dp.Scale = new(6); + dp.ScaleMode = ScaleMode.ByTime; + dp.Color = isTarget ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + private async static void drawSpreadDir(bool isStackFirst, int myIndex, ScriptAccessory accessory) + { + var spreadTime = isStackFirst ? 6100 : 3000; + + if (isStackFirst) + { + await Task.Delay(6500); + DebugMsg($"开始绘制分散位置", accessory); + drawSpreadPos(mb_isLeftCleave, spreadTime - 400, myIndex, accessory); + } + else + drawSpreadLine(spreadTime, myIndex, accessory); + return; + } + + private static void drawSpreadLine(int spreadTime, int myIndex, ScriptAccessory accessory) + { + Vector3[] safePos = new Vector3[8]; + safePos[0] = new Vector3(100, 0, 90); + safePos[1] = new Vector3(110, 0, 100); + safePos[2] = new Vector3(90, 0, 100); + safePos[3] = new Vector3(100, 0, 100); + safePos[4] = new Vector3(90, 0, 90); + safePos[5] = new Vector3(110, 0, 90); + safePos[6] = new Vector3(90, 0, 110); + safePos[7] = new Vector3(110, 0, 110); + + for (int i = 0; i < 8; i++) + { + var dp = AssignDp.dirPos2Pos(new Vector3(100, 0, 100), safePos[i], spreadTime - 500, spreadTime + 500, $"分散方向{i}", accessory); + dp.Scale = new(1.5f); + dp.Color = myIndex == i ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + } + + private static void drawSpreadPos(bool isLeftCleave, int spreadTime, int myIndex, ScriptAccessory accessory) + { + Vector3[] safePos = new Vector3[8]; + // 右边 + safePos[0] = new Vector3(100.5f, 0, 80); + safePos[1] = new Vector3(110, 0, 80); + safePos[2] = new Vector3(100.5f, 0, 90); + safePos[3] = new Vector3(100.5f, 0, 100); + safePos[4] = new Vector3(110, 0, 90); + safePos[5] = new Vector3(110, 0, 100); + safePos[6] = new Vector3(110, 0, 110); + safePos[7] = new Vector3(100.5f, 0, 110); + + if (!isLeftCleave) + { + for (int i = 0; i < 8; i++) + { + safePos[i] = DirectionCalc.FoldPointLR(safePos[i], 100); + } + } + + for (int i = 0; i < 8; i++) + { + var dp0 = AssignDp.drawStatic(safePos[i], 0, 0, spreadTime, $"分散位置{i}", accessory); + dp0.Scale = new(1f); + dp0.Color = myIndex == i ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + } + + var dp = AssignDp.dirPos(safePos[myIndex], 0, spreadTime, $"分散位置指路{myIndex}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + // 5076.4 "Forcible Trifire/Forcible Difreeze" Ability { id: ["79BD", "79BE"], source: "Hephaistos" } + [ScriptMethod(name: "本体:一冰火引导范围", eventType: EventTypeEnum.StatusAdd, eventCondition: ["Param:regex:^(47[68])$"])] + public void MB_ForceFireFreeze(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.NA1) return; + var tid = @event.TargetId(); + var isFireFirst = @event.Param() == 476; + var myIndex = accessory.Data.PartyList.IndexOf(accessory.Data.Me); + + var dp = accessory.Data.GetDefaultDrawProperties(); + // 两人火,三组 + for (uint i = 1; i < 4; i++) + { + dp.Name = $"一紫圈火-{i}"; + dp.Scale = new(5); + dp.Owner = tid; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerFarestOrder; + dp.CentreOrderIndex = i; + dp.Color = mb_isNATarget[myIndex] ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + dp.Delay = isFireFirst ? 0 : 6100; + dp.DestoryAt = isFireFirst ? 6000 : 6100; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + // 三人冰,两组 + for (uint i = 1; i < 3; i++) + { + dp.Name = $"一紫圈冰-{i}"; + dp.Scale = new(5); + dp.Owner = tid; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.CentreOrderIndex = i + 1; + dp.Color = mb_isNATarget[myIndex] ? accessory.Data.DefaultDangerColor : accessory.Data.DefaultSafeColor; + dp.Delay = isFireFirst ? 6100 : 0; + dp.DestoryAt = isFireFirst ? 6100 : 6000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "本体:分身炮判断第几行", eventType: EventTypeEnum.SetObjPos, eventCondition: ["SourceDataId:15079"], userControl: false)] + public void MB_IllusionBeamLineIdx(Event @event, ScriptAccessory accessory) + { + var pos = JsonConvert.DeserializeObject(@event["SourcePosition"]); + if (pos.Z > 100) return; + // 只需判断一次即可,后续判断都无影响 + mb_NA1_isLine1Safe = pos.Z < 90 ? false : true; + } + + [ScriptMethod(name: "本体:一冰火指路提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["Param:regex:^(47[68])$"])] + public void MB_ForceFireFreezeGuide(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.NA1) return; + var isFireFirst = @event.Param() == 476; + var myIndex = IndexHelper.getMyIndex(accessory); + DebugMsg($"{(mb_NA1_isLine1Safe ? "先第一行安全" : "先第二行安全")}", accessory); + + // 我是TN且TN固定,或,我是DPS但DPS固定 + var isFixed = (mb_NA1_isTNFixed && myIndex < 4) || (!mb_NA1_isTNFixed && myIndex >= 4); + + Vector3[] dxFixed = [new(-8.5f, 0, 0), new(-6.5f, 0, 0), new(6.5f, 0, 0), new(8.5f, 0, 0)]; + Vector3 destinationPoint1; + Vector3 destinationPoint2; + Vector3 dzFreeze = mb_NA1_isLine1Safe ? new(0, 0, -0.5f) : new(0, 0, 0.5f); + Vector3 dzFire = mb_NA1_isLine1Safe ? new(0, 0, -9.5f) : new(0, 0, 9.5f); + + if (isFixed) + { + // 无脑固定组 + var biasIdx = myIndex >= 4 ? myIndex - 4 : myIndex; + if (myIndex < 4) + { + if (myIndex == 0) biasIdx = 0; + else if (myIndex == 1) biasIdx = 3; + else if (myIndex == 2) biasIdx = 1; + else if (myIndex == 3) biasIdx = 2; + } + destinationPoint1 = (Vector3)new(100, 0, 90) + dxFixed[biasIdx] + dzFreeze; + destinationPoint2 = (Vector3)new(100, 0, 90) + dxFixed[biasIdx] - dzFreeze; + accessory.Method.TextInfo($"无脑组,固定站位", 10000); + } + else if (mb_isNATarget[myIndex]) + { + // 紫圈组 + destinationPoint1 = (Vector3)new(100, 0, 90) + dzFreeze; + destinationPoint2 = (Vector3)new(100, 0, 90) - dzFreeze; + accessory.Method.TextInfo($"紫圈组,场中固定站位", 10000); + } + else + { + // 动脑组 + int biasIdx; + if (myIndex < 4) + { + List TN_priority = new List { 1, 2, 0, 3 }; + List TN_NATarget = new List { mb_isNATarget[0], mb_isNATarget[1], mb_isNATarget[2], mb_isNATarget[3] }; + + int firstFalseIdx = TN_NATarget.IndexOf(false); + int lastFalseIdx = TN_NATarget.LastIndexOf(false); + + if (firstFalseIdx != -1 && lastFalseIdx != -1) + { + bool firstFalseHigh = TN_priority[firstFalseIdx] < TN_priority[lastFalseIdx]; + bool isFirstFalse = myIndex == firstFalseIdx; + biasIdx = ((isFirstFalse && firstFalseHigh) || (!isFirstFalse && !firstFalseHigh)) ? 0 : 3; + } + else + { + // 没有找到 false,自求多福 + biasIdx = 3; + } + } + else + { + // 从后往前找到第一个false,如果是我,那我就是优先级低 + int lastFalseIdx = Array.LastIndexOf(mb_isNATarget, false); + biasIdx = myIndex == lastFalseIdx ? 3 : 0; + } + + destinationPoint1 = isFireFirst ? (Vector3)new(100, 0, 90) + dzFire : (Vector3)new(100, 0, 90) + dxFixed[biasIdx] + dzFreeze; + destinationPoint2 = isFireFirst ? (Vector3)new(100, 0, 90) + dxFixed[biasIdx] - dzFreeze : (Vector3)new(100, 0, 90) - dzFire; + accessory.Method.TextInfo($"动脑组,优先级站位", 10000); + } + + var dp = accessory.Data.GetDefaultDrawProperties(); + + dp.Name = $"一冰火指路-1"; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = destinationPoint1; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"一冰火指路-2"; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = destinationPoint2; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 6100; + dp.DestoryAt = 6100; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + } + + [ScriptMethod(name: "本体:分身炮", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31371"])] + public void MB_IllusionBeam(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"分身炮"; + dp.Scale = new(10, 50); + dp.Owner = sid; + dp.Color = accessory.Data.DefaultDangerColor; + dp.DestoryAt = 5700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + #endregion + + #region 本体:一运 + + [ScriptMethod(name: "本体:概念支配", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31148"], userControl: false)] + public void MB_HighConceptReady(Event @event, ScriptAccessory accessory) + { + mb_conceptFinNum = 0; + mb_phase = mb_phase switch + { + MB_Phase.NA1 => MB_Phase.HC1, + MB_Phase.NA2 => MB_Phase.HC2, + _ => MB_Phase.HC1 + }; + } + + // 5118.6 "High Concept 1" Ability { id: "710A", source: "Hephaistos" } window 20,20 + // 3330 D02 = Imperfection: Alpha BUFF + // 3331 D03 = Imperfection: Beta + // 3332 D04 = Imperfection: Gamma + // 3333 D05 = Perfection: Alpha 图案 + // 3334 D06 = Perfection: Beta + // 3335 D07 = Perfection: Gamma + // 3336 D08 = Inconceivable (temporary after merging) 禁止合成 + // 3337 D09 = Winged Conception (alpha + beta) 绿风 + // 3338 D0A = Aquatic Conception (alpha + gamma) 蓝水 + // 3339 D0B = Shocking Conception (beta + gamma) 紫雷马 + // 3340 D0C = Fiery Conception (ifrits, alpha + alpha) 伊芙利特 + // 3341 D0D = Toxic Conception (snake, beta + beta) 双蛇 + // 3342 D0E = Growing Conception (tree together, gamma + gamma) 大树 + // 3343 D0F = Immortal Spark (feather) 不死鸟前置羽毛 + // 3344 D10 = Immortal Conception (phoenix) 不死鸟 + // 3345 D11 = Solosplice 单人分摊 + // 3346 D12 = Multisplice 双人分摊 + // 3347 D13 = Supersplice 三人分摊 + [ScriptMethod(name: "本体:一运记录Buff", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3346|3347|3331|3330|3332)$"], userControl: false)] + public void MB_BRC_HighConcept1(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.HC1) return; + var tid = @event.TargetId(); + var dur = @event.DurationMilliseconds(); + // if (!int.TryParse(@event["DurationMilliseconds"], out var dur)) return; + var isLong = dur > 9000; + switch (@event["StatusID"]) + { + case "3346": + mb_hc1_sid[0] = tid; + break; + case "3347": + mb_hc1_sid[1] = tid; + break; + case "3330": + if (isLong) + { + mb_hc1_sid[3] = tid; + } + else + { + mb_hc1_sid[2] = tid; + } + break; + case "3331": + if (isLong) + { + mb_hc1_sid[5] = tid; + } + else + { + mb_hc1_sid[4] = tid; + } + break; + case "3332": + if (isLong) + { + mb_hc1_sid[7] = tid; + } + else + { + mb_hc1_sid[6] = tid; + } + break; + default: + break; + } + } + + [ScriptMethod(name: "本体:一运初始站位指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:31148"])] + public void MB_HC1_GuidePhase0(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.HC1) return; + Task.Delay(10900).ContinueWith(t => + { + var myHCIndex = mb_hc1_sid.IndexOf(accessory.Data.Me); + var dp = accessory.Data.GetDefaultDrawProperties(); + + Vector3 destinationPoint; + switch (myHCIndex) + { + case 0: destinationPoint = new(108, 0, 90); break; + case 1: destinationPoint = new(100, 0, 100); break; + case 2: destinationPoint = new(80, 0, 80); break; + case 3: destinationPoint = new(108, 0, 90); break; + case 4: destinationPoint = new(80, 0, 120); break; + case 5: destinationPoint = new(100, 0, 100); break; + case 6: destinationPoint = new(120, 0, 120); break; + case 7: destinationPoint = new(100, 0, 100); break; + default: destinationPoint = new(100, 0, 100); break; + } + + dp.Name = $"一运初始指路"; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = destinationPoint; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + }); + + } + + [ScriptMethod(name: "本体:记录塔色", eventType: EventTypeEnum.EnvControl, eventCondition: ["Id:00020001"], userControl: false)] + public void MB_TowerColorRecord(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.HC1) return; + lock (this) + { + // if (!int.TryParse(@event["Index"], System.Globalization.NumberStyles.HexNumber, null, out var tower_color)) return; + var tower_color = @event.Index(); + mb_conceptFinNum++; + // accessory.Method.SendChat($"/e mb_conceptFinNum = {mb_conceptFinNum}"); + + if (mb_conceptFinNum == 2) + { + if (tower_color >= 26 && tower_color <= 35) + { + // mb_towerColor = "purple"; + mb_TwoStackDestination = new(90, 0, 110); + mb_ThreeStackDestination = new(110, 0, 110); + mb_UnmergeDestination = new(90, 0, 90); + mb_mentionTxt = "2人→B,3人→C,拼图→A"; + mb_joinMerge = [false, true, true]; + + mb_alphaLongFollower = mb_hc1_sid[2]; + mb_betaLongFollower = mb_hc1_sid[0]; + mb_gammaLongFollower = mb_hc1_sid[1]; + } + else if (tower_color >= 36 && tower_color <= 45) + { + // mb_towerColor = "blue"; + mb_TwoStackDestination = new(90, 0, 90); + mb_ThreeStackDestination = new(110, 0, 110); + mb_UnmergeDestination = new(90, 0, 110); + mb_mentionTxt = "2人→A,3人→C,拼图→B"; + mb_joinMerge = [true, false, true]; + + mb_alphaLongFollower = mb_hc1_sid[0]; + mb_betaLongFollower = mb_hc1_sid[4]; + mb_gammaLongFollower = mb_hc1_sid[1]; + } + else if (tower_color >= 46 && tower_color <= 55) + { + // mb_towerColor = "green"; + mb_TwoStackDestination = new(90, 0, 90); + mb_ThreeStackDestination = new(90, 0, 110); + mb_UnmergeDestination = new(110, 0, 110); + mb_mentionTxt = "2人→A,3人→B,拼图→C"; + mb_joinMerge = [true, true, false]; + + mb_alphaLongFollower = mb_hc1_sid[0]; + mb_betaLongFollower = mb_hc1_sid[1]; + mb_gammaLongFollower = mb_hc1_sid[6]; + } + else return; + + if (HC1_ChatGuidance) + { + accessory.Method.SendChat($"/p {mb_mentionTxt} "); + } + else + { + accessory.Method.SendChat($"/e {mb_mentionTxt}"); + } + + } + else if (mb_conceptFinNum == 6) + { + if (tower_color >= 26 && tower_color <= 35) + { + // mb_towerColor = "purple"; + mb_joinMerge = [false, true, true]; + } + else if (tower_color >= 36 && tower_color <= 45) + { + // mb_towerColor = "blue"; + mb_joinMerge = [true, false, true]; + } + else if (tower_color >= 46 && tower_color <= 55) + { + // mb_towerColor = "green"; + mb_joinMerge = [true, true, false]; + } + else return; + } + } + } + + [ScriptMethod(name: "本体:一运第一次合成指路", eventType: EventTypeEnum.EnvControl, eventCondition: ["Id:00020001"])] + public void MB_HC1_GuidePhase1(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.HC1) return; + Task.Delay(50).ContinueWith(t => + { + if (mb_sideCleaveNum == 1 && mb_conceptFinNum == 2) + { + var myHCIndex = mb_hc1_sid.IndexOf(accessory.Data.Me); + + bool shouldJoinMerge = (myHCIndex == 2 && mb_joinMerge[0]) || (myHCIndex == 4 && mb_joinMerge[1]) || (myHCIndex == 6 && mb_joinMerge[2]); + bool shouldAvoidMerge = (myHCIndex == 2 && !mb_joinMerge[0]) || (myHCIndex == 4 && !mb_joinMerge[1]) || (myHCIndex == 6 && !mb_joinMerge[2]); + + var dp0 = accessory.Data.GetDefaultDrawProperties(); + if (shouldJoinMerge) + { + dp0.Name = "参与合成指路"; + dp0.Scale = new(0.5f); + dp0.Owner = accessory.Data.Me; + dp0.TargetPosition = new(100, 0, 100); + dp0.ScaleMode = ScaleMode.YByDistance; + dp0.Color = accessory.Data.DefaultSafeColor; + dp0.Delay = 0; + dp0.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + + dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = "参与合成区域"; + dp0.Scale = new(5); + dp0.Position = new(100, 0, 100); + dp0.Color = accessory.Data.DefaultSafeColor; + dp0.Delay = 0; + dp0.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + accessory.Method.TextInfo($"参与合成", 5000); + + } + else if (shouldAvoidMerge) + { + dp0 = accessory.Data.GetDefaultDrawProperties(); + dp0.Name = "避开合成区域"; + dp0.Scale = new(5); + dp0.Position = new(100, 0, 100); + dp0.Color = accessory.Data.DefaultDangerColor; + dp0.Delay = 0; + dp0.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + accessory.Method.TextInfo($"避开合成", 5000, true); + } + } + }); + } + + [ScriptMethod(name: "本体:一运合成后站位指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(3119[12])$"])] + public void MB_HC1_GuidePhase2(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.HC1) return; + if (mb_conceptFinNum != 2) return; + var myHCIndex = mb_hc1_sid.IndexOf(accessory.Data.Me); + bool joinedMerge = false; + Vector3 safeDestination = new(110, 0, 90); + + var dp = accessory.Data.GetDefaultDrawProperties(); + switch (myHCIndex) + { + case 0: + dp.TargetPosition = mb_TwoStackDestination; + break; + case 1: + dp.TargetPosition = mb_ThreeStackDestination; + break; + case 2: + joinedMerge = mb_joinMerge[0]; + dp.TargetPosition = joinedMerge ? safeDestination : mb_UnmergeDestination; + break; + case 3: + dp.TargetPosition = new(80, 0, 80); + break; + case 4: + joinedMerge = mb_joinMerge[1]; + dp.TargetPosition = joinedMerge ? safeDestination : mb_UnmergeDestination; + break; + case 5: + dp.TargetPosition = new(80, 0, 120); + break; + case 6: + joinedMerge = mb_joinMerge[2]; + dp.TargetPosition = joinedMerge ? safeDestination : mb_UnmergeDestination; + break; + case 7: + dp.TargetPosition = new(120, 0, 120); + break; + } + dp.Name = $"一运第二阶段指路"; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 3500; + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + if (joinedMerge) + { + dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"一运第二阶段安全区"; + dp.Scale = new(8); + dp.Position = safeDestination; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 3500; + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "本体:一运第二次合成指路", eventType: EventTypeEnum.EnvControl, eventCondition: ["Id:00020001"])] + public void MB_HC1_GuidePhase3(Event @event, ScriptAccessory accessory) + { + if (mb_phase != MB_Phase.HC1) return; + Task.Delay(50).ContinueWith(t => + { + if (mb_sideCleaveNum == 2 && mb_conceptFinNum == 6) + { + var myHCIndex = mb_hc1_sid.IndexOf(accessory.Data.Me); + + bool shouldJoinMergeUp = (myHCIndex == 3 && mb_joinMerge[0]) || + (accessory.Data.Me == mb_betaLongFollower && mb_joinMerge[1]) || + (accessory.Data.Me == mb_gammaLongFollower && mb_joinMerge[2]); + + bool shouldJoinMergeDown = (accessory.Data.Me == mb_alphaLongFollower && mb_joinMerge[0]) || + (myHCIndex == 5 && mb_joinMerge[1]) || + (myHCIndex == 7 && mb_joinMerge[2]); + + var dp = accessory.Data.GetDefaultDrawProperties(); + if (shouldJoinMergeUp) + { + dp.Name = $"参与上半场合成指路"; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = new(100, 0, 90); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + dp.Name = $"参与上半场合成区域"; + dp.Scale = new(5); + dp.Owner = accessory.Data.Me; + dp.Position = new(100, 0, 90); + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + accessory.Method.TextInfo($"参与上半场合成", 6000); + + } + else if (shouldJoinMergeDown) + { + dp.Name = $"参与下半场合成指路"; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = new(100, 0, 110); + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + dp.Name = $"参与下半场合成区域"; + dp.Scale = new(5); + dp.Owner = accessory.Data.Me; + dp.Position = new(100, 0, 110); + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + accessory.Method.TextInfo($"参与下半场合成", 6000); + } + else + { + dp.Name = $"避开上半场合成区域"; + dp.Scale = new(5); + dp.Owner = accessory.Data.Me; + dp.Position = new(100, 0, 90); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + dp.Name = $"避开下半场合成区域"; + dp.Scale = new(5); + dp.Owner = accessory.Data.Me; + dp.Position = new(100, 0, 110); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = 0; + dp.DestoryAt = 6000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + accessory.Method.TextInfo($"避开合成", 6000, true); + } + } + }); + } + + #endregion + + #region 本体:万象 + + [ScriptMethod(name: "本体:万象灰烬指路与分散预警", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:30189"])] + public void MB_LimitlessDesolation(Event @event, ScriptAccessory accessory) + { + mb_phase = MB_Phase.LD; + var dp = accessory.Data.GetDefaultDrawProperties(); + for (int i = 0; i < accessory.Data.PartyList.Count(); i++) + { + dp.Name = $"万象分散-{i}"; + dp.Scale = new(6); + dp.Owner = accessory.Data.PartyList[i]; + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.6f); + dp.Delay = 0; + dp.DestoryAt = 20000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + var myIndex = IndexHelper.getMyIndex(accessory); + drawLDSpreadDir(5000, myIndex, accessory); + } + + private static void drawLDSpreadDir(int castTime, int myIndex, ScriptAccessory accessory) + { + Vector3[] safePos = new Vector3[8]; + safePos[0] = new Vector3(90, 0, 80); + safePos[1] = new Vector3(80, 0, 90); + safePos[2] = new Vector3(80, 0, 100); + safePos[3] = new Vector3(90, 0, 110); + safePos[4] = DirectionCalc.FoldPointLR(safePos[0], 100); + safePos[5] = DirectionCalc.FoldPointLR(safePos[1], 100); + safePos[6] = DirectionCalc.FoldPointLR(safePos[2], 100); + safePos[7] = DirectionCalc.FoldPointLR(safePos[3], 100); + + for (int i = 0; i < 8; i++) + { + var dp0 = AssignDp.drawStatic(safePos[i], 0, 0, castTime, $"分散位置{i}", accessory); + dp0.Scale = new(1f); + dp0.Color = myIndex == i ? posColorPlayer.V4.WithW(2f) : posColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp0); + } + + var dp = AssignDp.dirPos(safePos[myIndex], 0, castTime, $"分散位置指路{myIndex}", accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "本体:万象灰烬放黄圈预警", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:30192"])] + public void MB_TyrantsFire(Event @event, ScriptAccessory accessory) + { + // 检测到易伤后,绘制放黄圈预警 + var tid = @event.TargetId(); + var tidx = accessory.Data.PartyList.IndexOf(tid); + accessory.Method.RemoveDraw($"万象分散-{tidx}"); + + if (tid != accessory.Data.Me) return; + + // 31368 + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"万象黄圈预警"; + dp.Scale = new(8); + dp.ScaleMode = ScaleMode.ByTime; + dp.Owner = tid; + dp.Color = ColorHelper.DelayDangerColor.V4; + dp.Delay = 0; + dp.DestoryAt = 8000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + /// + /// 万象踩塔数据,包含“塔是第几个出现的”,“塔相对坐标”,“哪个玩家负责踩塔” + /// + public class LD_Tower + { + public int TowerIdx { get; set; } + public int Row { get; set; } + public int Col { get; set; } + public int PlayerIdx { get; set; } + public LD_Tower(int tidx, int row, int col, int playerIdx) + { + TowerIdx = tidx; + Row = row; + Col = col; + PlayerIdx = playerIdx; + } + } + + [ScriptMethod(name: "本体:万象塔记录", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:800375AB", "Id:00020001", "Index:regex:^(000000(0[9ABC]|4[CDEF]|5[0145]))$"], userControl: false)] + public void MB_LDTowerRecord(Event @event, ScriptAccessory accessory) + { + lock (mb_LD_towerOrder) + { + var idx = @event.Index(); + int Row; + int Col; + (Row, Col) = idx switch + { + 0x9 => (2, 2), + 0xA => (2, 3), + 0xB => (3, 2), + 0xC => (3, 3), + 0x4C => (1, 1), + 0x4D => (1, 2), + 0x4E => (1, 3), + 0x4F => (1, 4), + 0x50 => (2, 1), + 0x51 => (2, 4), + 0x54 => (3, 1), + 0x55 => (3, 4), + _ => (0, 0) + }; + mb_LD_towerOrder.Add(new LD_Tower(mb_LD_towerOrder.Count(), Row, Col, -1)); + DebugMsg($"捕捉到第{mb_LD_towerOrder.Count()}座塔({Row}行, {Col}列)的生成。", accessory); + } + } + + [ScriptMethod(name: "本体:万象踩塔绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:30192"])] + public async void MB_LDPlayerRecord(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + if (@event.TargetIndex() != 1) return; + var pidx = IndexHelper.getPlayerIdIndex(tid, accessory); + mb_LD_playerOrder.Add(pidx); + DebugMsg($"捕捉到易伤:{IndexHelper.getPlayerJobByIndex(pidx)}", accessory); + // 第{mb_LD_playerOrder.Count()}个 + + await Task.Delay(1000); + + lock (mb_LD_towerOrder) + { + var myIndex = IndexHelper.getMyIndex(accessory); + for (int i = 0; i < mb_LD_towerOrder.Count(); i++) + { + if (mb_LD_towerOrder[i].PlayerIdx != -1) continue; + + var isTN = pidx <= 3; + var isLeftTower = mb_LD_towerOrder[i].Col <= 2; + + if ((isTN && isLeftTower) || (!isTN && !isLeftTower)) + { + mb_LD_towerOrder[i].PlayerIdx = pidx; + if (myIndex == pidx) + drawLDDir(mb_LD_towerOrder[i], accessory); + } + else + continue; + } + } + + } + + private static void drawLDDir(LD_Tower tower, ScriptAccessory accessory) + { + // 在自身脚下出现黄圈前,将塔范围标记为危险区 + var dp_tdanger = assignDp_Tower(tower.Row, tower.Col, accessory); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp_tdanger); + + // 塔中危险区消失后,再标记为安全区 + var dp_tsafe = assignDp_Tower(tower.Row, tower.Col, accessory); + dp_tsafe.Color = accessory.Data.DefaultSafeColor; + dp_tsafe.Delay = 7000; + // 故意写长消失时间,用envcontrol消除 + dp_tsafe.DestoryAt = 30000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp_tsafe); + + // 塔中危险区消失后,人物到塔指路 + var dp_tdir = assignDp_TowerDir(tower.Row, tower.Col, accessory); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp_tdir); + return; + } + + private static DrawPropertiesEdit assignDp_Tower(int row, int col, ScriptAccessory accessory) + { + Vector3 tower_center = new Vector3(75 + col * 10, 0, 75 + row * 10); + var delay = 0; + var destoryAt = 7000; // 8000 - task delay的100 + var dp = AssignDp.drawStatic(tower_center, 0, delay, destoryAt, $"塔{row}{col}", accessory); + dp.Scale = new(4); + dp.Color = ColorHelper.colorRed.V4; + return dp; + } + + private static DrawPropertiesEdit assignDp_TowerDir(int row, int col, ScriptAccessory accessory) + { + Vector3 tower_center = new Vector3(75 + col * 10, 0, 75 + row * 10); + var delay = 7000; + var destoryAt = 30000; // 8000 - task delay的100 + var dp = AssignDp.dirPos(tower_center, delay, destoryAt, $"塔指路{row}{col}", accessory); + return dp; + } + + [ScriptMethod(name: "本体:万象塔消失", eventType: EventTypeEnum.EnvControl, eventCondition: ["DirectorId:800375AB", "Id:00080004", "Index:regex:^(000000(0[9ABC]|4[CDEF]|5[0145]))$"], userControl: false)] + public void MB_LDTowerRemove(Event @event, ScriptAccessory accessory) + { + var idx = @event.Index(); + int Row; + int Col; + (Row, Col) = idx switch + { + 0x9 => (2, 2), + 0xA => (2, 3), + 0xB => (3, 2), + 0xC => (3, 3), + 0x4C => (1, 1), + 0x4D => (1, 2), + 0x4E => (1, 3), + 0x4F => (1, 4), + 0x50 => (2, 1), + 0x51 => (2, 4), + 0x54 => (3, 1), + 0x55 => (3, 4), + _ => (0, 0) + }; + accessory.Method.RemoveDraw($"塔指路{Row}{Col}"); + accessory.Method.RemoveDraw($"塔{Row}{Col}"); + } + + // EnvControl记录 + // 800375AB 00020001 + // Index + // 00000005 (2,2) 石头 // 00000009 (2,2) 塔 + // 00000006 (2,3) 石头 // 0000000A (2,3) 塔 + // 00000007 (3,2) 石头 // 0000000B (3,2) 塔 + // 00000008 (3,3) 石头 // 0000000C (3,3) 塔 + + // 00000046 (1,1) 石头 // 0000004C (1,1) 塔 + // 00000047 (1,2) 石头 // 0000004D (1,2) 塔 + // 00000048 (1,3) 石头 // 0000004E (1,3) 塔 + // 00000049 (1,4) 石头 // 0000004F (1,4) 塔 + // 0000004A (2,1) 石头 // 00000050 (2,1) 塔 + // 0000004B (2,4) 石头 // 00000051 (2,4) 塔 + + // 00000052 (3,1) 石头 // 00000054 (3,1) 塔 + // 00000053 (3,4) 石头 // 00000055 (3,4) 塔 + + // State + // 00020001 生成 + // 00200010 踩到 + // 00400001 出去 + // 00080004 消失 + + #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 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 TargetId(this Event @event) + { + return ParseHexId(@event["TargetId"], out var id) ? id : 0; + } + + public static uint TargetIndex(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetIndex"]); + } + + 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Index(this Event @event) + { + return ParseHexId(@event["Index"], out var id) ? id : 0; + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var id) ? id : 0; + } + + 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 DirectionCalc +{ + // 以北为0建立list + // InnGame List Dir + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Dir = Pi - InnGame (+ 2pi) + + /// + /// 将游戏基角度(以南为0,逆时针增加)转为逻辑基角度(以北为0,顺时针增加) + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float BaseInnGame2DirRad(float radian) + { + float r = (float)Math.PI - radian; + if (r < 0) r = (float)(r + 2 * Math.PI); + if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float BaseDirRad2InnGame(float radian) + { + float r = (float)Math.PI - radian; + if (r < Math.PI) r = (float)(r + 2 * Math.PI); + if (r > Math.PI) r = (float)(r - 2 * Math.PI); + return r; + } + + /// + /// 输入逻辑基角度,获取逻辑方位 + /// + /// 逻辑基角度 + /// 方位总数 + /// 逻辑基角度对应的逻辑方位 + public static int DirRadRoundToDirs(float radian, int dirs) + { + var r = Math.Round(radian / (2f / dirs * Math.PI)); + if (r == dirs) r = r - dirs; + return (int)r; + } + + /// + /// 输入坐标,获取正分割逻辑方位(以右上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionFloorToDirs(Vector3 point, Vector3 center, int dirs) + { + // 正分割,0°为分界线,将360°分为dirs份 + var r = Math.Floor(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取斜分割逻辑方位(以正上为0) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 该坐标点对应的逻辑方位 + public static int PositionRoundToDirs(Vector3 point, Vector3 center, int dirs) + { + // 斜分割,0° return 0,将360°分为dirs份 + var r = Math.Round(dirs / 2 - dirs / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirs; + return (int)r; + } + + /// + /// 将角度转为弧度 + /// + /// 角度值 + /// 对应的弧度值 + public static float angle2Rad(float angle) + { + // 输入角度转为弧度 + float radian = (float)(angle * Math.PI / 180); + return radian; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + + // 另一种方案待验证 + // var nextPos = Vector3.Transform((point - center), Matrix4x4.CreateRotationY(radian)) + center; + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(Vector3 center, Vector3 new_point) + { + // 找到某点到中心的弧度 + float radian = MathF.PI - MathF.Atan2(new_point.X - center.X, new_point.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointLR(Vector3 point, int centerx) + { + Vector3 v3 = new(2 * centerx - point.X, point.Y, point.Z); + return v3; + } +} + +public static class IndexHelper +{ + /// + /// 输入玩家dataid,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int getPlayerIdIndex(uint pid, ScriptAccessory accessory) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int getMyIndex(ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataid,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string getPlayerJobByID(uint pid, ScriptAccessory accessory) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var a = accessory.Data.PartyList.IndexOf(pid); + switch (a) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// + public static string getPlayerJobByIndex(int idx) + { + switch (idx) + { + case 0: return "MT"; + case 1: return "ST"; + case 2: return "H1"; + case 3: return "H2"; + case 4: return "D1"; + case 5: return "D2"; + case 6: return "D3"; + case 7: return "D4"; + default: return "unknown"; + } + } +} + +public static class ColorHelper +{ + public static ScriptColor colorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor colorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor colorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + + // 门神 龙龙凤凤延迟危险区颜色,紫色 + public static ScriptColor DelayDangerColor = new ScriptColor { V4 = new Vector4(1f, 0.2f, 1f, 1.5f) }; + // 门神 蛇蛇位置颜色 + public static ScriptColor GorgonColor = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; +} + +public static class AssignDp +{ + /// + /// 返回自己指向某目标地点的dp,可修改dp.TargetPosition, dp.Scale + /// + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos(Vector3 target_pos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回起始地点指向某目标地点的dp,可修改dp.Position, dp.TargetPosition, dp.Scale + /// + /// 起始地点 + /// 指向地点 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirPos2Pos(Vector3 start_pos, Vector3 target_pos, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(0.5f); + dp.Position = start_pos; + dp.TargetPosition = target_pos; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回自己指向某目标对象的dp,可修改dp.TargetObject, dp.Scale + /// + /// 指向目标对象 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit dirTarget(uint target_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(0.5f); + dp.Owner = accessory.Data.Me; + dp.TargetObject = target_id; + dp.ScaleMode = ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象仇恨相关的dp,可修改dp.TargetResolvePattern, dp.TargetOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawTargetOrder(uint owner_id, uint order_idx, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.TargetOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回与某对象距离相关的dp,可修改dp.CentreResolvePattern, dp.CentreOrderIndex, dp.Owner + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCenterOrder(uint owner_id, uint order_idx, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.CentreResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.CentreOrderIndex = order_idx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + /// + /// 返回owner与target的dp,可修改 dp.Owner, dp.TargetObject, dp.Scale + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawOwner2Target(uint owner_id, uint target_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 40); + dp.Owner = owner_id; + dp.TargetObject = target_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawCircle(uint owner_id, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5); + dp.Owner = owner_id; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 起始位置,通常为场地中心 + /// 旋转角度,以北为0度顺时针 + /// 延时delay ms出现 + /// 绘图自出现起,经destoryAt ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit drawStatic(Vector3 center, float radian, int delay, int destoryAt, string name, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new(5, 20); + dp.Position = center; + dp.Rotation = DirectionCalc.BaseDirRad2InnGame(radian); + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destoryAt; + return dp; + } +} + + +#endregion \ No newline at end of file diff --git a/lianying1997/07-Dawntrail/FRU/FRU_Patch.cs b/lianying1997/07-Dawntrail/FRU/FRU_Patch.cs new file mode 100644 index 0000000..317d972 --- /dev/null +++ b/lianying1997/07-Dawntrail/FRU/FRU_Patch.cs @@ -0,0 +1,2974 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using System.Xml.Linq; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Lumina.Excel.Sheets; + +namespace UsamisKodakku.Script._07_DawnTrail.FRU; + +[ScriptType(name: Name, territorys: [1238], guid: "3076a62b-127e-468e-96d3-4f1d559857ec", + version: Version, author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class FruPatch +{ + private const string NoteStr = + """ + v0.0.0.12 + 指挥模式对P3二运有效。 + 若开启指挥模式,将执行近战优化标点。(固定MT左与D1右,并非固定MT左与ST右!) + 不论MT或ST引导,双T都会收到引导方向提示。 + 若发生问题请携ARR反馈。 + """; + + private const string Name = "FRU_Patch [光暗未来绝境战 补丁]"; + private const string Version = "0.0.0.12"; + private const string DebugVersion = "a"; + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + private const bool Debugging = false; + private static readonly bool LocalTest = false; + private static readonly bool LocalStrTest = false; // 本地不标点,仅用字符串表示。 + + [UserSetting("Debug模式,非开发用请关闭")] + public static bool DebugMode { get; set; } = false; + + [UserSetting("站位提示圈绘图-普通颜色")] + public static ScriptColor PosColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public static ScriptColor PosColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) }; + + [UserSetting("P1 - 罪壤堕优先级")] + public static FallOfFaithPriorityEnum FallOfFaithPriority { get; set; } = FallOfFaithPriorityEnum.H_T_D_H; + public enum FallOfFaithPriorityEnum + { + H_T_D_H, + T_H_D, + H_T_D, + } + [UserSetting("P2 - 光爆策略")] + public static LightRampageStgEnum LightRampageStg { get; set; } = LightRampageStgEnum.Grey9_灰9式; + public enum LightRampageStgEnum + { + Hexagram_正六芒星, + Grey9_灰9式, + } + + [UserSetting("P3 - 启示录MT/ST优先级互换")] + public static bool ApoTankPriorSwap { get; set; } = false; + + [UserSetting("P3 - 启示录策略")] + public static ApoStgEnum ApoStg { get; set; } = ApoStgEnum.CrowdFirst_人群车头; + public enum ApoStgEnum + { + CrowdFirst_人群车头, + CrownFirst_MTD1皇帝车头, + } + + [UserSetting("指挥模式(总开关)")] + public static bool CaptainMode { get; set; } = false; + + [UserSetting("指挥模式 - 开启【P1乐园绝技】指挥")] + public static bool UosCaptainMode { get; set; } = false; + + [UserSetting("指挥模式 - 开启【P1罪壤堕(DB闲固)】指挥")] + public static bool FofCaptainMode { get; set; } = false; + + [UserSetting("指挥模式 - 开启【P3二运启示录分组】指挥")] + public static bool ApoCaptainMode { get; set; } = false; + + private static readonly Random Random = new(); // 随机测试用 + private int rdTarget = -1; // 随机测试用 + private volatile List _recorded = new bool[20].ToList(); // 被记录flag + private static List _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + + private enum FruPhase + { + Init, + P1A_UtopianSky, // P1A 乐园绝技 + P1B_FallOfFaith, // P1B 罪壤堕 + P1C_BurntStrike, // P1C 燃烧击 + P2A_DiamondDust, // P2A 钻石星辰 + P2B_Mirror, // P2B 镜中奇遇 + P2C_LightRampant, // P2B 光爆 + P2D_AbsoluteZero, // P2C 绝对零度 + P3A_UltimateRelativity, // P3A 时间压缩 + P3B_Apocalypse, // P3B 地火 + P4A_DarklitDragonsong, // P4A 光暗龙诗 + P4B_CrystallizeTime, // P4B 时间结晶 + P5A_FulgentBlade, // P5A 光尘之剑地火 + P5B_ParadiseRegained, // P5B 复乐园踩塔 + P5C_PolarizingStrike, // P5C 星灵之剑挡枪 + } + + private static readonly Vector3 Center = new Vector3(100, 0, 100); + private FruPhase _fruPhase = FruPhase.Init; + private static PriorityDict _pd = new PriorityDict(); + private static Counter _ct = new Counter(); + private static Apocalypse _apo = new Apocalypse(); + private static UlRelativity _ulr = new UlRelativity(); + + private static List _fragTargets = []; + + private const int Mt = 0; + private const int St = 1; + private const int H1 = 2; + private const int H2 = 3; + private const int D1 = 4; + private const int D2 = 5; + private const int D3 = 6; + private const int D4 = 7; + + private const ulong FragmentDataId = 17841; + + public void Init(ScriptAccessory accessory) + { + _fruPhase = FruPhase.Init; + + rdTarget = -1; + _recorded = new bool[20].ToList(); + _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + + accessory.DebugMsg($"Init {Name} v{Version}{DebugVersion} Success.\n{UpdateInfo}", DebugMode); + accessory.Method.MarkClear(); + LocalMarkClear(accessory); + accessory.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo"], userControl: false)] + public void EchoDebugActive(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + var msg = @event.Message(); + switch (msg) + { + case "=TST": + accessory.DebugMsg($"Debug操作。", DebugMode); + break; + + case "=CLEAR": + accessory.DebugMsg($"删除绘图与标点 Local。", DebugMode); + LocalMarkClear(accessory); + accessory.Method.RemoveDraw(".*"); + break; + } + } + + #region P1 绝命战士 + + [ScriptMethod(name: "---- 《P1:绝命战士》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["ActionId:Hello1aya2World"], + userControl: true)] + public void SplitLine_FateBreaker(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.DebugMsg($"debug 怎会有人点到这个", DebugMode); + LocalMarkClear(accessory); + accessory.Method.RemoveDraw(".*"); + } + + #region P1.1 乐园绝技 + + [ScriptMethod(name: "乐园绝技阶段转换", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4015[45])$"], + userControl: Debugging)] + public void UtopianSkyPhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P1A_UtopianSky; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + // 初始化优先级class + _pd.Init(accessory, "乐园绝技"); + // TN在上,DPS在下,MTD1负责换 + _pd.AddPriorities([3, 0, 1, 2, 4, 5, 6, 7]); + } + + [ScriptMethod(name: "乐园绝技分摊头标", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(00F9)$"], + userControl: true)] + public void UtopianSkyStackMark(Event ev, ScriptAccessory sa) + { + /* + * 攻击1:被连线,上半场 + * 攻击2:被连线,下半场 + * 禁止1:仅会出现在D1头上,被点则说明要去上半场 + * 禁止2:仅会出现在MT头上,被点则说明要去下半场 + * 初始优先级设置:_pd.AddPriorities([3, 0, 1, 2, 4, 5, 6, 7]); + * 只要检测到目标被连线,对应优先级+10。 + */ + if (_fruPhase != FruPhase.P1A_UtopianSky) return; + var tid = ev.TargetId; + var tidx = sa.GetPlayerIdIndex(tid); + sa.Log.Debug($"乐园绝技 {ev.Id}, {ev.Id0()}"); + lock (_pd) + { + _pd.Priorities[tidx] += 10; + _pd.AddActionCount(); + } + if (!_pd.IsActionCountEqualTo(2)) return; + _pd.ShowPriorities(); + + // 从小到大排序,index 0-2闲人在上,3-5闲人在下,6接线在上,7接线在下。 + { + var upTetherTarget = _pd.SelectSpecificPriorityIndex(6); + var downTetherTarget = _pd.SelectSpecificPriorityIndex(7); + // 标点 + MarkPlayerByIdx(sa, upTetherTarget.Key, MarkType.Attack1, UosCaptainMode); + MarkPlayerByIdx(sa, downTetherTarget.Key, MarkType.Attack2, UosCaptainMode); + // 发送Debug信息 + var str = "\n"; + str += $"上:{upTetherTarget.Key} ({sa.GetPlayerJobByIndex(upTetherTarget.Key)})\n"; + str += $"下:{downTetherTarget.Key} ({sa.GetPlayerJobByIndex(downTetherTarget.Key)})\n"; + sa.DebugMsg(str, DebugMode); + } + + var remainPlayers = _pd.SelectSmallPriorityIndices(6); + // 交换检查,只需要查index为2或3的情况 + if (remainPlayers[2].Key == D1) + { + // 满足该条件,代表D1在上。 + var str = $"玩家{remainPlayers[2].Key} ({sa.GetPlayerJobByIndex(remainPlayers[2].Key)}需交换。)"; + sa.DebugMsg(str, DebugMode); + MarkPlayerByIdx(sa, remainPlayers[2].Key, MarkType.Stop1, UosCaptainMode); + } + + if (remainPlayers[3].Key == Mt) + { + // 满足该条件,代表MT在下。 + var str = $"玩家{remainPlayers[3].Key} ({sa.GetPlayerJobByIndex(remainPlayers[3].Key)}需交换。)"; + sa.DebugMsg(str, DebugMode); + MarkPlayerByIdx(sa, remainPlayers[3].Key, MarkType.Stop2, UosCaptainMode); + } + + // 若想后续应用指路可直接调用remainPlayers,Key代表职业位置,Value为优先级。 + } + + #endregion P1.1 乐园绝技 + + #region P1.2 罪壤堕 + + [ScriptMethod(name: "光焰圆光阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40170)$"], + userControl: Debugging)] + public void BurnishedGloryPhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = _fruPhase switch + { + FruPhase.P1B_FallOfFaith => FruPhase.P1C_BurntStrike, + _ => FruPhase.P1B_FallOfFaith + }; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + + // 删除乐园绝技、罪壤堕的头标 + MarkClear(accessory); + + // 建立罪壤堕设置 + if (_fruPhase != FruPhase.P1B_FallOfFaith) return; + // 初始化优先级class + _pd.Init(accessory, "罪壤堕"); + + // 标点优先级设置 + List priority = FallOfFaithPriority switch + { + FallOfFaithPriorityEnum.T_H_D => [0, 1, 2, 3, 4, 5, 6, 7], + FallOfFaithPriorityEnum.H_T_D => [2, 3, 0, 1, 4, 5, 6, 7], + FallOfFaithPriorityEnum.H_T_D_H => [1, 2, 0, 7, 3, 4, 5, 6], + _ => [2, 3, 0, 1, 4, 5, 6, 7], + }; + + _pd.AddPriorities(priority); + } + + [ScriptMethod(name: "罪壤堕头标与指路(DB闲固)", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(00F9|011F)$"], + userControl: true)] + public void FallOfFaithMarkAndGuidance(Event ev, ScriptAccessory sa) + { + /* + * 自用,DB闲固 + * 分为雷组、火组、闲人组 + * 雷:+10,+20,+30,+40(n) + * 火:+80,+70,+60,+50(8-n) + * 奇数:+0 + * 偶数:+100 + * 数字从小到大,可标锁1、禁1、锁2、禁2(分别对应左、左上、右、右上) + */ + // const uint fire = 0x00F9; + const uint lightning = 0x011F; + + if (_fruPhase != FruPhase.P1B_FallOfFaith) return; + var tid = ev.TargetId; + var tidx = sa.GetPlayerIdIndex(tid); + var tetherType = ev.Id0(); + sa.Log.Debug($"罪壤堕 {ev.Id}, {ev.Id0()}"); + + // 后续可根据myIndex进行指路 + var myIndex = sa.GetMyIndex(); + + lock (_pd) + { + // 连线出现先+1 + _pd.AddActionCount(); + sa.Log.Debug($"罪壤堕 连线数{_pd.ActionCount} 连了{sa.GetPlayerJobByIndex(tidx)}({tidx}) 是{(tetherType == lightning ? "雷" : "火")}线"); + + // 根据注释加对应数值 + var addNum = _pd.ActionCount % 2 == 0 ? 100 : 0; + addNum += 10 * (tetherType == lightning ? _pd.ActionCount : 9 - _pd.ActionCount); + _pd.Priorities[tidx] += addNum; + + switch (_pd.ActionCount) + { + case 1: + MarkPlayerByIdx(sa, tidx, tetherType == lightning ? MarkType.Bind1 : MarkType.Stop1, FofCaptainMode); + if (tidx == myIndex) FallOfFaithGuidance(sa, tetherType == lightning ? 4 : 5); + break; + case 2: + MarkPlayerByIdx(sa, tidx, tetherType == lightning ? MarkType.Bind2 : MarkType.Stop2, FofCaptainMode); + if (tidx == myIndex) FallOfFaithGuidance(sa, tetherType == lightning ? 6 : 7); + break; + case 3: + // 因为此时有5个闲人,减1消除偏移 + MarkPlayerByIdx(sa, tidx, (_pd.FindPriorityIndexOfKey(tidx) - 1) % 2 == 0 ? MarkType.Bind1 : MarkType.Stop1, FofCaptainMode); + if (tidx == myIndex) FallOfFaithGuidance(sa, (_pd.FindPriorityIndexOfKey(tidx) - 1) % 2 == 0 ? 4 : 5); + break; + case 4: + // 此时有4个闲人 + MarkPlayerByIdx(sa, tidx, _pd.FindPriorityIndexOfKey(tidx) % 2 == 0 ? MarkType.Bind2 : MarkType.Stop2, FofCaptainMode); + if (tidx == myIndex) FallOfFaithGuidance(sa, _pd.FindPriorityIndexOfKey(tidx) % 2 == 0 ? 6 : 7); + break; + } + } + + if (!_pd.IsActionCountEqualTo(4)) return; + _pd.ShowPriorities(); + + Thread.MemoryBarrier(); + + // index 0~3 闲人,index 4~7, 可标锁1、禁1、锁2、禁2(分别对应左、左上、右、右上) + MarkPlayerByIdx(sa, _pd.SelectSpecificPriorityIndex(0).Key, MarkType.Attack1, FofCaptainMode); + MarkPlayerByIdx(sa, _pd.SelectSpecificPriorityIndex(1).Key, MarkType.Attack2, FofCaptainMode); + MarkPlayerByIdx(sa, _pd.SelectSpecificPriorityIndex(2).Key, MarkType.Attack3, FofCaptainMode); + MarkPlayerByIdx(sa, _pd.SelectSpecificPriorityIndex(3).Key, MarkType.Attack4, FofCaptainMode); + + var myPriority = _pd.FindPriorityIndexOfKey(myIndex); + + // 雷火线交换提示。取优先级值十位数,若相差1,不换;相差2,换。 + if (myPriority >= 4) + { + var myPriVal = _pd.SelectSpecificPriorityIndex(myPriority).Value; + var ptPriority = myPriority % 2 == 0 ? myPriority + 1 : myPriority - 1; + var ptPriVal = _pd.SelectSpecificPriorityIndex(ptPriority).Value; + var subtract = Math.Abs(myPriVal / 10 % 10 - ptPriVal / 10 % 10); + sa.DebugMsg($"优先级 (({myPriority}){myPriVal} - ({ptPriority}){ptPriVal}) / 10 % 10 = {subtract}", DebugMode); + if (subtract != 2) return; + FallOfFaithGuidance(sa, myPriority, true); + sa.Method.TextInfo($"连线玩家准备交换", 3000, true); + } + else + { + // 闲人指路 + FallOfFaithGuidance(sa, myPriority); + } + } + + private void FallOfFaithGuidance(ScriptAccessory accessory, int priority, bool swapHint = false) + { + const int lightLeft = 4; + const int fireLeft = 5; + const int lightRight = 6; + const int fireRight = 7; + + const int freeLeftMiddle = 0; + const int freeLeftBottom = 1; + const int freeRightBottom = 2; + const int freeRightMiddle = 3; + + // baseMiddlePos为左中坐标,bias为偏置坐标,用于上、下、左。 + var baseMiddlePos = new Vector3(95, 0, 100); + const float bias = 2f; + + var tpos = priority switch + { + lightLeft => baseMiddlePos, + lightRight => baseMiddlePos.FoldPointHorizon(Center.X), + freeLeftMiddle => baseMiddlePos - new Vector3(bias / 2, 0, 0), + freeRightMiddle => (baseMiddlePos - new Vector3(bias / 2, 0, 0)).FoldPointHorizon(Center.X), + + fireLeft => baseMiddlePos - new Vector3(0, 0, bias), + fireRight => (baseMiddlePos - new Vector3(0, 0, bias)).FoldPointHorizon(Center.X), + freeLeftBottom => (baseMiddlePos - new Vector3(0, 0, bias)).FoldPointVertical(Center.Z), + freeRightBottom => (baseMiddlePos - new Vector3(0, 0, bias)).FoldPointHorizon(Center.X).FoldPointVertical(Center.Z), + + _ => baseMiddlePos, + }; + + if (!swapHint) + { + var dp = accessory.DrawGuidance(tpos, 0, 20000, $"Usami-罪壤堕初始优先级{priority}指路"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + else + { + var swapPos = priority switch + { + fireLeft => baseMiddlePos, + fireRight => baseMiddlePos.FoldPointHorizon(Center.X), + lightLeft => baseMiddlePos - new Vector3(0, 0, bias), + lightRight => (baseMiddlePos - new Vector3(0, 0, bias)).FoldPointHorizon(Center.X), + _ => baseMiddlePos, + }; + + var dp = accessory.DrawGuidance(tpos, swapPos, 0, 20000, $"Usami-罪壤堕交换优先级{priority}指路", isSafe: false); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + + [ScriptMethod(name: "罪壤堕头标指路移除(DEBUG ONLY)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:40141"], + userControl: Debugging, suppress: 10000)] + public void FallOfFaithRemove(Event @event, ScriptAccessory accessory) + { + if (_fruPhase != FruPhase.P1B_FallOfFaith) return; + MarkClear(accessory); + accessory.Method.RemoveDraw($"Usami-罪壤堕.*"); + } + + #endregion 罪壤堕 + + #endregion P1 绝命战士 + + #region P2 希瓦 + + [ScriptMethod(name: "---- 《P2:希瓦·米特隆》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["ActionId:Hello1aya2World"], + userControl: true)] + public void SplitLine_Shiva(Event @event, ScriptAccessory accessory) + { + } + + #region P2.1 钻石星辰 + + [ScriptMethod(name: "钻石星辰阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40197)$"], + userControl: Debugging)] + public void DiamondDustPhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P2A_DiamondDust; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + #endregion P2.1 钻石星辰 + + #region P2.2 镜子 + + [ScriptMethod(name: "镜中奇遇阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40179)$"], + userControl: Debugging)] + public void MirrorPhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P2B_Mirror; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + #endregion P2.2 镜子 + + #region P2.3 光爆 + + [ScriptMethod(name: "光之失控阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40212)$"], + userControl: Debugging)] + public void LightRampartPhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P2C_LightRampant; + _pd.Init(accessory, "光爆"); + + List priorities = LightRampageStg switch + { + LightRampageStgEnum.Grey9_灰9式 => [20, 42, 2, 24, 13, 33, 11, 31], + LightRampageStgEnum.Hexagram_正六芒星 => [0, 1, 2, 3, 10, 11, 12, 13], + _ => [20, 42, 2, 24, 13, 33, 11, 31] + }; + + // 灰9式放泥 + /* 数小在左,数大在右 + * 00 10 20 30 40 + * 0 MT + * 1 D3 D4 + * 2 H1 ST + * 3 D1 D2 + * 4 H2 + */ + + // 六芒星式放泥 + /* 数小在左下,数大在右上 + * 00 01 02 03 + * 00 ST H1 + * 00 MT H2 + * 10 D1 D4 + * 10 D2 D3 + */ + + _pd.AddPriorities(priorities); + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + [ScriptMethod(name: "光之失控放泥", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0177)$"], + userControl: true)] + public void LuminousHammerGuidance(Event @event, ScriptAccessory accessory) + { + if (_fruPhase != FruPhase.P2C_LightRampant) return; + lock (_pd) + { + var tid = @event.TargetId; + var tidx = accessory.GetPlayerIdIndex(tid); + _pd.AddActionCount(); + _pd.AddPriority(tidx, 100); + } + + if (_pd.ActionCount != 2) return; + _pd.ShowPriorities(); + + // +100将优先级值放到最后两位,小的左(下),大的右(上) + var tLeft = _pd.SelectSpecificPriorityIndex(6).Key; + var tRight = _pd.SelectSpecificPriorityIndex(7).Key; + var myIndex = accessory.GetMyIndex(); + if ((myIndex != tLeft) && (myIndex != tRight)) return; + + // 此处leftRoute指左/上路线 + List leftRoute = LightRampageStg switch + { + LightRampageStgEnum.Grey9_灰9式 => + [ + new Vector3(92, 0, 100), + new Vector3(94.6f, 0, 94), + new Vector3(100, 0, 92), + new Vector3(105.6f, 0, 92), + new Vector3(111.3f, 0, 88.7f) + ], + LightRampageStgEnum.Hexagram_正六芒星 => + [ + new Vector3(100, 0, 92).RotatePoint(Center, 22.5f.DegToRad()), + new Vector3(100, 0, 92).RotatePoint(Center, 67.5f.DegToRad()), + new Vector3(100, 0, 92).RotatePoint(Center, 112.5f.DegToRad()), + new Vector3(100, 0, 92).RotatePoint(Center, 157.5f.DegToRad()), + new Vector3(100, 0, 92).RotatePoint(Center, 157.5f.DegToRad()) + new Vector3(0, 0, 8.5f), + ], + _ => + [ + new Vector3(92, 0, 100), + new Vector3(94.6f, 0, 94), + new Vector3(100, 0, 92), + new Vector3(105.6f, 0, 92), + new Vector3(111.3f, 0, 88.7f) + ], + }; + + List rightRoute = + [ + leftRoute[0].PointCenterSymmetry(Center), + leftRoute[1].PointCenterSymmetry(Center), + leftRoute[2].PointCenterSymmetry(Center), + leftRoute[3].PointCenterSymmetry(Center), + leftRoute[4].PointCenterSymmetry(Center), + ]; + + for (var i = 0; i < 4; i++) + { + var dp1 = accessory.DrawGuidance(leftRoute[i], leftRoute[i + 1], 0, 14000, $"Usami-光爆左{i}{i + 1}", isSafe: myIndex == tLeft); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp1); + var dp2 = accessory.DrawGuidance(rightRoute[i], rightRoute[i + 1], 0, 14000, $"Usami-光爆右{i}{i + 1}", isSafe: myIndex == tRight); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp2); + } + + var dp = accessory.DrawGuidance(myIndex == tLeft ? leftRoute[0] : rightRoute[0], 0, 8000, $"Usami-光爆初始"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + } + + [ScriptMethod(name: "光爆球爆炸时间", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40219)$"], + userControl: true)] + public void LightBalloonExplode(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P2C_LightRampant) return; + var sid = ev.SourceId; + var dp = sa.DrawCircle(sid, 11, 2500, 2500, $"光球{sid}", true); + dp.Color = ColorHelper.ColorRed.V4.WithW(4f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion P2.3 光爆 + + #region P2.4 绝对零度 + + [ScriptMethod(name: "绝对零度阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40224)$"], + userControl: Debugging)] + public void AbsoluteZeroPhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P2D_AbsoluteZero; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + [ScriptMethod(name: "暗水晶不可选中", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:17828"], + userControl: true)] + public async void DarkCrystalUntargetable(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P2D_AbsoluteZero) return; + var sid = ev.SourceId; + await Task.Delay(500); + sa.SetTargetable(sa.GetById(sid), false); + } + + #endregion P2.4 绝对零度 + + #endregion P2 希瓦 + + #region P3 盖娅 + + [ScriptMethod(name: "---- 《P3:暗之巫女》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["ActionId:Hello1aya2World"], + userControl: true)] + public void SplitLine_Gaia(Event ev, ScriptAccessory accessory) + { + } + + #region P3.1 时间压缩 + + [ScriptMethod(name: "时间压缩阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40266)$"], + userControl: Debugging)] + public void UlrPhaseChange(Event ev, ScriptAccessory sa) + { + _fruPhase = FruPhase.P3A_UltimateRelativity; + sa.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + _ct.Init(sa, "时间压缩"); + _pd.Init(sa, "时间压缩"); + _ulr.Init(sa, _pd, _ct); + _pd.AddPriorities([0, 1, 2, 3, 104, 105, 106, 107]); + _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + } + + [ScriptMethod(name: "输出时间压缩信息(DEBUG ONLY)", eventType: EventTypeEnum.NpcYell, eventCondition: ["ActionId:Hello1aya2World"], + userControl: Debugging)] + public void UlrMessagePrint(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + _ulr.ShowMessage(); + } + + [ScriptMethod(name: "长短火类型记录(DEBUG ONLY)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(2455|2462)$"], + userControl: Debugging)] + public void UlrDarkFireTypeRecord(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3A_UltimateRelativity) return; + + // 11s, 21s, 31s + const uint fire = 2455; + const uint ice = 2462; + const uint fireShort = 11000 - 2000; + const uint fireMid = 21000 - 2000; + const uint fireLong = 31000 - 2000; + + var dur = ev.DurationMilliseconds(); + var tidx = sa.GetPlayerIdIndex(ev.TargetId); + var stid = ev.StatusId; + sa.Log.Debug($"检测到{sa.GetPlayerJobByIndex(tidx)}的 {(stid == fire ? "火" : "冰")} {dur}"); + + lock (_ulr) + { + if (stid == fire) + { + if (dur > fireShort) + _pd.AddPriority(tidx, 10); + if (dur > fireMid) + _pd.AddPriority(tidx, 10); + if (dur > fireLong) + _pd.AddPriority(tidx, 10); + } + else + { + // TN视为短火,DPS视为长火 + _pd.AddPriority(tidx, tidx <= 3 ? 10 : 30); + } + } + } + + [ScriptMethod(name: "沙漏连线记录(DEBUG ONLY)", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(0085|0086)$"], + userControl: Debugging)] + public void UlrHourglassTetherRecord(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3A_UltimateRelativity) return; + const uint fast = 0x86; + const uint slow = 0x85; + lock (_ulr) + { + var spos = ev.SourcePosition; + var sdir = spos.Position2Dirs(Center, 8); + _ulr.TetherDirection[sdir] = ev.Id0() == fast ? 1 : 2; + sa.Log.Debug($"检测到{sdir}方向沙漏,ev.Id = {ev.Id} ev.Id0 = {ev.Id0()}, {(ev.Id0() == fast ? "快" : "慢")}"); + _ct.AddCounter(); + + if (_ct.Number != 5) return; + sa.Log.Debug($"五根线记录完毕,开始寻找相对北。"); + _ulr.BuildTrueDirection(); + } + } + + [ScriptMethod(name: "眩晕时自动面向", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:4163"], + userControl: true, suppress: 10000)] + public void UlrAutoFace(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3A_UltimateRelativity) return; + var myDir = _ulr.GetDirection(sa.GetMyIndex()); + sa.Log.Debug($"眩晕,触发自动面向。"); + sa.SetRotation(sa.Data.MyObject, (myDir * 45f).DegToRad().Game2Logic()); + } + + public class UlRelativity + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa { get; set; } = null!; + public int RelativeNorth { get; set; } = -1; + // ReSharper disable once NullableWarningSuppressionIsUsed + public PriorityDict pd { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public Counter ct { get; set; } = null!; + public List TetherDirection { get; set; } = Enumerable.Repeat(0, 8).ToList(); // 0无,1加速,2减速 + public List RelativeDirection { get; set; } = [4, 6, 5, 3, 7, 1, 2, 0]; + public List TrueDirection { get; set; } = Enumerable.Repeat(0, 8).ToList(); + public void Init(ScriptAccessory accessory, PriorityDict priorityDict, Counter counter) + { + sa = accessory; + pd = priorityDict; + ct = counter; + TetherDirection = Enumerable.Repeat(0, 8).ToList(); + TrueDirection = Enumerable.Repeat(0, 8).ToList(); + RelativeNorth = -1; + } + + public void BuildTrueDirection() + { + // 找到减速灯 + var slowTetherIdx = TetherDirection.IndexOf(2, 0); + var checkIdx1 = (slowTetherIdx - 2 + 8) % 8; + var checkIdx2 = (slowTetherIdx + 2 + 8) % 8; + RelativeNorth = TetherDirection[checkIdx1] == 1 ? checkIdx1 : checkIdx2; + sa.Log.Debug($"根据减速线{slowTetherIdx}计算相对北,检查{checkIdx1}与{checkIdx2},得到{RelativeNorth}为相对北"); + + for (int i = 0; i < 8; i++) + { + var jobIdx = pd.SelectSpecificPriorityIndex(i).Key; + var dir = (RelativeDirection[i] + RelativeNorth) % 8; + TrueDirection[jobIdx] = dir; + sa.Log.Debug($"字典顺序{i}, {sa.GetPlayerJobByIndex(jobIdx)}({jobIdx}), 需去方位{dir}。"); + } + } + + public void ShowMessage() + { + var str = "\n ---- [时间压缩] ----\n"; + str += $"相对北:{RelativeNorth}。\n"; + str += $"各职能位置:{sa.BuildListStr(TrueDirection)}"; + + sa.Log.Debug(str); + } + + public int GetDirection(int jobIdx) + { + return TrueDirection[jobIdx]; + } + + } + + #endregion P3.1 时间压缩 + + #region P3.2 地火 + + [ScriptMethod(name: "输出启示录信息(DEBUG ONLY)", eventType: EventTypeEnum.NpcYell, eventCondition: ["ActionId:Hello1aya2World"], + userControl: Debugging)] + public void ApoMessagePrint(Event @ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + _apo.ShowMessage(); + } + + [ScriptMethod(name: "启示录阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40269)$"], + userControl: Debugging)] + public void ApoPhaseChange(Event @ev, ScriptAccessory sa) + { + _fruPhase = FruPhase.P3B_Apocalypse; + _pd.Init(sa, "启示录"); + _ct.Init(sa, "启示录外部标点"); + MarkClear(sa); + _apo.Init(sa, _pd); + // ST引导超级跳的话,建议开启 + _pd.AddPriorities(ApoTankPriorSwap ? [1, 0, 2, 3, 7, 6, 5, 4] : [0, 1, 2, 3, 7, 6, 5, 4]); // 初始THD优先级 + _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + sa.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + [ScriptMethod(name: "水分摊类型记录(DEBUG ONLY)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2461"], userControl: Debugging)] + public void DarkWaterTypeRecord(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + var dur = ev.DurationMilliseconds(); + var tidx = sa.GetPlayerIdIndex(ev.TargetId); + + // 10s, 29s, 38s + const uint waterShort = 10000 - 2000; + const uint waterMid = 29000 - 2000; + const uint waterLong = 38000 - 2000; + + lock (_pd) + { + if (dur > waterShort) + _pd.AddPriority(tidx, 10); + if (dur > waterMid) + _pd.AddPriority(tidx, 10); + if (dur > waterLong) + _pd.AddPriority(tidx, 10); + _pd.AddActionCount(); + + if (_pd.ActionCount == 6) + { + _apo.Grouping(); + } + } + } + + [ScriptMethod(name: "启示录检测外部标点", eventType: EventTypeEnum.Marker, eventCondition: ["Operate:Add", "Id:regex:^(0[1234678]|11)$"], + userControl: Debugging)] + public void DarkWaterMarkerFromOut(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + if (CaptainMode && ApoCaptainMode) return; + _events[(int)EventIdx.ApoGrouping].WaitOne(); + + lock (_apo) + { + _ct.AddCounter(); + var mark = ev.Id0(); + var tid = ev.TargetId; + var tidx = sa.GetPlayerIdIndex(tid); + + var groupIdx = mark switch + { + 0x1 => 0, // Atk1 + 0x2 => 2, // Atk2 + 0x3 => 4, // Atk3 + 0x4 => 6, // Atk4 + 0x6 => 1, // Bind1 + 0x7 => 3, // Bind2 + 0x8 => 5, // Bind3 + 0x11 => 7, // Square + _ => 0, + }; + + sa.DebugMsg($"检测到外部标点{mark}给玩家{sa.GetPlayerJobByIndex(tidx)}", DebugMode); + + // 直接修改定义,此时优先值已无意义 + _apo.TempGroup[groupIdx] = new KeyValuePair(tidx, 0); + + // 如果不是标8人,作废。 + if (_ct.Number != 8) return; + sa.DebugMsg($"检测到外部标点标满8人,覆盖_apo.Group分组逻辑。", DebugMode); + _apo.Group = [.. _apo.TempGroup]; + _events[(int)EventIdx.ApoPreciseGrouping].Set(); + _apo.GroupingFixed = true; + } + } + + [ScriptMethod(name: "水分摊范围提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:2461"], userControl: true)] + public void DarkWaterRange(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + // 程序内部确定分组 -> 检测是否完成精确分组 -> 范围提示 + _events[(int)EventIdx.ApoGrouping].WaitOne(); + _events[(int)EventIdx.ApoPreciseGrouping].WaitOne(1000); + + var tid = ev.TargetId; + var tidx = sa.GetPlayerIdIndex(tid); + var dur = ev.DurationMilliseconds(); + + var isSameGroup = _apo.GetMyGroup() == _apo.GetPlayerGroup(tidx); + var delayed = !_apo.GroupingFixed; + + var dp = sa.DrawCircle(tid, 6, (int)dur - 3000 - (delayed ? 1000 : 0), 3000, $"Usami-狂水{tidx}", true); + dp.Color = isSameGroup ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "分组交换提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:40270"], + userControl: true)] + public void DarkWaterSwapHint(Event @ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + // 程序内部确定分组 -> 检测是否完成精确分组 -> 交换提示 + _events[(int)EventIdx.ApoGrouping].WaitOne(); + _events[(int)EventIdx.ApoPreciseGrouping].WaitOne(1000); + + var myIndex = sa.GetMyIndex(); + var myPreviousGroup = myIndex < 4 ? 0 : 1; // 0 左 1 右 + var myCurrentGroup = _apo.GetMyGroup(); + + var dp = sa.DrawGuidance(_apo.IsInLeftGroup(myIndex) ? new Vector3(93, 0, 100) : new Vector3(107, 0, 100), 0, 3000, $"Usami-狂水位置"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + var str = _apo.GroupingFixed ? "依照头标" : ApoTankPriorSwap ? "依照优先级(ST>MT)低换" : "依照优先级低换"; + sa.Method.TextInfo($"{(myPreviousGroup == myCurrentGroup ? "不换" : "交换")}({str})", 3000, true); + sa.DebugMsg($"{(myPreviousGroup == myCurrentGroup ? "不换" : "交换")}({str})", DebugMode); + } + + [ScriptMethod(name: "碎灵一击范围与分散提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40288"], userControl: true)] + public void SpiritTakerHint(Event @ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + var myIndex = sa.GetMyIndex(); + for (int i = 0; i < 8; i++) + { + var dp = sa.DrawCircle(sa.Data.PartyList[i], 5, 1000, 2000, $"Usami-碎灵一击范围"); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + // 左侧人群集合点 + Vector3 leftCenter = new(92, 0, 100); + Vector3 rightCenter = leftCenter.FoldPointHorizon(Center.X); + List spreadTargetPos = Enumerable.Repeat(new Vector3(0, 0, 0), 20).ToList(); + + List rot = [0f, 0f, 180f, 180f, -60f, -60f, -120f, -120f]; + + for (int i = 0; i < 8; i += 2) + { + spreadTargetPos[i] = leftCenter.ExtendPoint(rot[i].DegToRad(), 8f); + spreadTargetPos[i + 1] = spreadTargetPos[i].PointCenterSymmetry(Center); + } + + sa.DebugMsg($"我在左 {_apo.IsInLeftGroup(myIndex)},我在右{_apo.IsInRightGroup(myIndex)}", DebugMode); + + for (int i = 0; i < 8; i++) + { + // 考虑到可能得预站位问题,废弃掉特定路线分散。 + var isSafe = (_apo.IsInLeftGroup(myIndex) && i % 2 == 0) || (_apo.IsInRightGroup(myIndex) && i % 2 == 1); + var startPos = spreadTargetPos[i].PointInOutside(i % 2 == 0 ? leftCenter : rightCenter, 6f); + var dp = sa.DrawGuidance(startPos, spreadTargetPos[i], 0, 3000, $"碎灵一击分散", isSafe: isSafe); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + } + } + + [ScriptMethod(name: "地火分散位置指路", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:40288"], userControl: true, suppress: 10000)] + public void ApoSpreadGuidance(Event @ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + + var myIndex = sa.GetMyIndex(); + var myGroupSafePosIdx = _apo.IsInCrowd(myIndex) ? -1 : 0; + int mySafeDir; + // 根据玩家所属位置,选择人群安全区位置。 + if (ApoStg == ApoStgEnum.CrownFirst_MTD1皇帝车头) + { + // 若玩家在左组,找位于北的皇帝安全区;否则找位于南的皇帝安全区 + int safeDir = (_apo.SafePoints[1] + 3) % 8; + int mySafeDirIdx = _apo.IsInLeftGroup(myIndex) ? (safeDir < 4 ? 1 : 3) : (safeDir >= 4 ? 1 : 3); + if (_apo.IsInCrowd(myIndex)) + mySafeDirIdx -= 1; + mySafeDir = _apo.SafePoints[mySafeDirIdx]; + } + else + { + // (ApoStg == ApoStgEnum.CrowdFirst_人群车头) + // 若玩家在左组,找位于北的人群安全区;否则找位于南的人群安全区 + int safeDir = (_apo.SafePoints[0] + 3) % 8; + int mySafeDirIdx = _apo.IsInLeftGroup(myIndex) ? (safeDir < 4 ? 0 : 2) : (safeDir >= 4 ? 0 : 2); + if (!_apo.IsInCrowd(myIndex)) + mySafeDirIdx += 1; + mySafeDir = _apo.SafePoints[mySafeDirIdx]; + } + + if (_apo.GroupingFixed && _apo.IsInCrowd(myIndex)) + myGroupSafePosIdx = _apo.GetMyGroupIdx() / 2 - 1; + + sa.DebugMsg($"玩家的安全方位为{mySafeDir}, 具体分散位置序列为{myGroupSafePosIdx}。", DebugMode); + + for (int i = 0; i < 4; i++) + { + List safePos = _apo.GetSafePos(_apo.SafePoints[i]); + for (int j = 0; j < (i % 2 == 0 ? 3 : 1); j++) + { + bool isSafe = mySafeDir == _apo.SafePoints[i]; + + if (_apo.GroupingFixed && _apo.IsInCrowd(myIndex)) + isSafe &= myGroupSafePosIdx == j; + + var dp = sa.DrawStaticCircle(safePos[j], isSafe ? sa.Data.DefaultSafeColor.WithW(3f) : sa.Data.DefaultDangerColor.WithW(3f), 0, 10000, $"Usami-分散位dir{i}-idx{j}", 0.5f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + + if (_apo.GroupingFixed && isSafe) + { + var dp0 = sa.DrawGuidance(safePos[j], 0, 10000, $"Usami-分散位dir{i}-idx{j}指引"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + } + } + } + } + + [ScriptMethod(name: "地火类型记录(DEBUG ONLY)", eventType: EventTypeEnum.ObjectEffect, eventCondition: ["Id1:4", "Id2:regex:^(16|64)$"], userControl: Debugging)] + public void ExaflareTypeRecord(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + + lock (_apo) + { + if (_apo.ActionCount >= 2) return; + var northApoDir = ev.SourcePosition.Position2Dirs(Center, 8); + var rot = ev.Id2(); + _apo.AddActionCount(); + + // 记录北方开始点与旋转方向 + if ((northApoDir + 3) % 8 < 4) + { + _apo.NorthStartPoint = northApoDir; + _apo.RotationDir = rot == 0x16 ? 1 : -1; + } + + if (_apo.ActionCount != 2) return; + _apo.GetSafeDirs(); + } + } + + [ScriptMethod(name: "暗夜舞蹈引导指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40181"], userControl: true)] + public void ApoDarkDanceGuidance(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + // 引导是在皇帝位引导,此处可直接取apo中MT与D1所站皇帝位的位置。 + + // _apo.SafePoints[1]与[3]是皇帝位所在方位,只需要箭头指出去即可。 + var tpos1 = new Vector3(100, 0, 80).RotatePoint(Center, (_apo.SafePoints[1] * 45f).DegToRad()); + var tpos2 = new Vector3(100, 0, 80).RotatePoint(Center, (_apo.SafePoints[3] * 45f).DegToRad()); + + var isTank = sa.Data.MyObject is { } o && o.IsTank(); + + sa.Method.TextInfo(isTank ? $"准备引导" : $"避开引导", 3000, true); + + var dp1 = sa.DrawGuidance(tpos1.PointInOutside(Center, 13f), tpos1, 0, 3000, $"Usami-暗夜舞蹈引导位1", scale: 3f, isSafe: false); + var dp2 = sa.DrawGuidance(tpos2.PointInOutside(Center, 13f), tpos2, 0, 3000, $"Usami-暗夜舞蹈引导位2", scale: 3f, isSafe: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + var dp11 = sa.DrawGuidance(tpos1.PointInOutside(Center, 13f), tpos1, 3000, 2000, $"Usami-暗夜舞蹈引导位1", scale: 3f, isSafe: isTank); + var dp22 = sa.DrawGuidance(tpos2.PointInOutside(Center, 13f), tpos2, 3000, 2000, $"Usami-暗夜舞蹈引导位2", scale: 3f, isSafe: isTank); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp11); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp22); + + var dp = sa.DrawTargetNearFarOrder(ev.SourceId, 1, false, 8, 8, 3000, 2000, $"Usami-暗夜舞蹈目标"); + dp.Color = isTank ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "暗夜舞蹈击退方向指引", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:40181", "TargetIndex:1"], userControl: true)] + public void ApoDarkDanceKnockBackGuidance(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + var sid = ev.SourceId; + // 将盖娅分成左右 + var isLeft = _apo.IsInLeftGroup(sa.GetMyIndex()); + var dp0 = sa.DrawGuidance(sid, 0, 1500, 3500, $"Usami-击退方向左", -155f.DegToRad().Ccw2Cw(), 3f, isSafe: isLeft); + var dp1 = sa.DrawGuidance(sid, 0, 1500, 3500, $"Usami-击退方向右", 155f.DegToRad().Ccw2Cw(), 3f, isSafe: !isLeft); + dp0.Scale = new Vector2(3f, 14f); + dp1.Scale = new Vector2(3f, 14f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp0); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + } + + [ScriptMethod(name: "启示录头标移除", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40282"], + userControl: Debugging)] + public void ApoMarkerRemove(Event @event, ScriptAccessory accessory) + { + if (_fruPhase != FruPhase.P3B_Apocalypse) return; + MarkClear(accessory); + } + + public class Apocalypse + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory accessory { get; set; } = null!; + public int ActionCount { get; set; } = 0; + public int NorthStartPoint { get; set; } = -1; + public List SafePoints { get; set; } = [-1, -1, -1, -1]; + public int RotationDir { get; set; } = 0; // 1顺,-1逆 + // ReSharper disable once NullableWarningSuppressionIsUsed + public List> Group { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public List> TempGroup { get; set; } = null!; + // ReSharper disable once NullableWarningSuppressionIsUsed + public PriorityDict Priorities { get; set; } = null!; + public bool GroupingFixed { get; set; } = false; // 分散位置是否精确到每个人 + public void Init(ScriptAccessory _accessory, PriorityDict _priorities) + { + accessory = _accessory; + Priorities = _priorities; + ActionCount = 0; + NorthStartPoint = -1; + SafePoints = [-1, -1, -1, -1]; + RotationDir = 0; + GroupingFixed = false; + } + + public void AddActionCount() + { + ActionCount++; + } + + public void Grouping() + { + TempGroup = Priorities.SelectSmallPriorityIndices(8); + + var str = ""; + str += $"Group初始左:{TempGroup[0].Key}, {TempGroup[2].Key}, {TempGroup[4].Key}, {TempGroup[6].Key}\n"; + str += $"Group初始右:{TempGroup[1].Key}, {TempGroup[3].Key}, {TempGroup[5].Key}, {TempGroup[7].Key}\n"; + accessory.DebugMsg(str, DebugMode); + + if (CaptainMode && ApoCaptainMode) + { + // 近战优待调整方式:若ST去右,D2无脑去左;若D2去左,ST无脑去右。 + // 因为加入优先级互换,后续注释中的"St"可被认为成"闲T(非引导T)" + // const int groupLeft = 0; + const int groupRight = 1; + var freeTank = ApoTankPriorSwap ? Mt : St; + var freeTankGroup = Priorities.FindPriorityIndexOfKey(freeTank) % 2; + var d2Group = Priorities.FindPriorityIndexOfKey(D2) % 2; + + if (freeTankGroup == d2Group) + { + // 当St与D2在同一组,三近战,需要换位 + // St在右组时,说明被Mt排挤,D2需与同Buff再次换位。此时D2的idx在后,D2搭档的idx在前。 + // St在左组时,说明D2被Mt排挤,St需与同Buff再次换位。此时St的idx在前,St搭档的idx在后。 + var targetIdx = freeTankGroup == groupRight ? Priorities.FindPriorityIndexOfKey(D2) : Priorities.FindPriorityIndexOfKey(freeTank); + var offset = freeTankGroup == groupRight ? -1 : 1; + (TempGroup[targetIdx + offset], TempGroup[targetIdx]) = (TempGroup[targetIdx], TempGroup[targetIdx + offset]); + } + + // 交换后对优先级重新处理,制作近战-远程优先级。 + List inRightGroup = new bool[8].ToList(); + for (int i = 0; i < 8; i++) + { + inRightGroup[TempGroup[i].Key] = i % 2 == groupRight; + } + + // 去掉优先级中代表水Buff的十位数,重新赋予十位数“左组”与“右组”的概念, + // 将个位数换为MT-ST-D1-D2, H1-H2-D3-D4的近小远大形式 + for (int i = 0; i < 8; i++) + Priorities.Priorities[i] = inRightGroup[i] ? 10 : 0; + + Priorities.AddPriorities(ApoTankPriorSwap ? [1, 0, 4, 5, 2, 3, 6, 7] : [0, 1, 4, 5, 2, 3, 6, 7]); + // 队长模式安排后 + Priorities.ShowPriorities(); + + // 重新设置优先级完毕后,按照特定顺序将元素放入tempGroup + // 左组1,右组1,左组2,右组2,左组3,右组3,左组4,右组4 + TempGroup = [Priorities.SelectSpecificPriorityIndex(0), Priorities.SelectSpecificPriorityIndex(4), + Priorities.SelectSpecificPriorityIndex(1), Priorities.SelectSpecificPriorityIndex(5), + Priorities.SelectSpecificPriorityIndex(2), Priorities.SelectSpecificPriorityIndex(6), + Priorities.SelectSpecificPriorityIndex(3), Priorities.SelectSpecificPriorityIndex(7)]; + + // 由于精确到了每一位玩家的站位,可设置fixed为true + GroupingFixed = true; + + // 标点 + MarkPlayerByIdx(accessory, TempGroup[0].Key, MarkType.Attack1, ApoCaptainMode); + MarkPlayerByIdx(accessory, TempGroup[2].Key, MarkType.Attack2, ApoCaptainMode); + MarkPlayerByIdx(accessory, TempGroup[4].Key, MarkType.Attack3, ApoCaptainMode); + MarkPlayerByIdx(accessory, TempGroup[6].Key, MarkType.Attack4, ApoCaptainMode); + MarkPlayerByIdx(accessory, TempGroup[1].Key, MarkType.Bind1, ApoCaptainMode); + MarkPlayerByIdx(accessory, TempGroup[3].Key, MarkType.Bind2, ApoCaptainMode); + MarkPlayerByIdx(accessory, TempGroup[5].Key, MarkType.Bind3, ApoCaptainMode); + MarkPlayerByIdx(accessory, TempGroup[7].Key, MarkType.Square, ApoCaptainMode); + + var str0 = ""; + str0 += $"Group最后左:{TempGroup[0].Key}, {TempGroup[2].Key}, {TempGroup[4].Key}, {TempGroup[6].Key}\n"; + str0 += $"Group最后右:{TempGroup[1].Key}, {TempGroup[3].Key}, {TempGroup[5].Key}, {TempGroup[7].Key}\n"; + accessory.DebugMsg(str0, DebugMode); + + _events[(int)EventIdx.ApoPreciseGrouping].Set(); + } + + // 若fixed为false(未开启指挥模式,或无人进行莫莫式标点),tempGroup的排序方式为:左无,右无,左短水,右短水,左中水,右中水,左长水,右长水。 + Group = [.. TempGroup]; + + var strLeft = $"{accessory.GetPlayerJobByIndex(Group[0].Key)},{accessory.GetPlayerJobByIndex(Group[2].Key)},{accessory.GetPlayerJobByIndex(Group[4].Key)},{accessory.GetPlayerJobByIndex(Group[6].Key)}"; + var strRight = $"{accessory.GetPlayerJobByIndex(Group[1].Key)},{accessory.GetPlayerJobByIndex(Group[3].Key)},{accessory.GetPlayerJobByIndex(Group[5].Key)},{accessory.GetPlayerJobByIndex(Group[7].Key)}"; + accessory.DebugMsg($"\n启示录分摊分组{(GroupingFixed ? "(Fixed)" : "")}:\n左组:{strLeft}\n右组:{strRight}", DebugMode); + + _events[(int)EventIdx.ApoGrouping].Set(); + } + + public void GetSafeDirs() + { + /* + * 将计算出的安全区 (dir+3)%8,0123为北侧,4567为南侧。 + * 3 + * 2 4 + * 1 5 + * 0 6 + * 7 + */ + + // 北地火旋转方向相反一格 + var dir = (NorthStartPoint + 8 - RotationDir) % 8; + bool isNorthSafePoint = (dir + 3) % 8 < 4; + + // 定义人群安全点 + SafePoints[isNorthSafePoint ? 0 : 2] = dir; + SafePoints[isNorthSafePoint ? 2 : 0] = GetSymmetricPoint(dir); + + // 进一步反向旋转,得到MT/D1位安全点 + SafePoints[1] = (SafePoints[0] + 8 - RotationDir) % 8; + SafePoints[3] = (SafePoints[2] + 8 - RotationDir) % 8; + + accessory.DebugMsg($"\n人群安全点:北{SafePoints[0]},南{SafePoints[2]}。\n皇帝安全点:北{SafePoints[1]},南{SafePoints[3]}。", DebugMode); + } + + public int GetSymmetricPoint(int dir) + { + // 地火顺时针旋转,安全区在逆时针一格。 + return (dir + 8 + 4) % 8; + } + + public int GetPlayerGroupIdx(int idx) + { + // 在Fixed情况下,该函数返回左前、右前、左后、右后的分组。 + return Group.FindIndex(i => i.Key == idx); + } + public int GetMyGroupIdx() => GetPlayerGroupIdx(accessory.GetMyIndex()); + public int GetPlayerGroup(int idx) => GetPlayerGroupIdx(idx) % 2; + public int GetMyGroup() => GetPlayerGroup(accessory.GetMyIndex()); + public bool IsInLeftGroup(int idx) => GetPlayerGroupIdx(idx) % 2 == 0; + public bool IsInRightGroup(int idx) => GetPlayerGroupIdx(idx) % 2 == 1; + public bool IsInCrowd(int idx) + { + return (idx != D1) && (idx != (ApoTankPriorSwap ? St : Mt)); + } + + public List GetSafePos(int dir) + { + List safePosList = [new(100, 0, 90.2f), new(104.5f, 0, 80.84f), new(95.5f, 0, 80.84f)]; + for (int i = 0; i < 3; i++) + safePosList[i] = safePosList[i].RotatePoint(Center, 45f.DegToRad() * dir); + return safePosList; + } + + public void ShowMessage() + { + var str = "\n ---- [启示录] ----\n"; + str += $"\n人群安全点:北{SafePoints[0]},南{SafePoints[2]}。\n皇帝安全点:北{SafePoints[1]},南{SafePoints[3]}。"; + + var strLeft = $"{accessory.GetPlayerJobByIndex(Group[0].Key)},{accessory.GetPlayerJobByIndex(Group[2].Key)},{accessory.GetPlayerJobByIndex(Group[4].Key)},{accessory.GetPlayerJobByIndex(Group[6].Key)}"; + var strRight = $"{accessory.GetPlayerJobByIndex(Group[1].Key)},{accessory.GetPlayerJobByIndex(Group[3].Key)},{accessory.GetPlayerJobByIndex(Group[5].Key)},{accessory.GetPlayerJobByIndex(Group[7].Key)}"; + + str += $"\n启示录分摊分组{(GroupingFixed ? "(Fixed)" : "")}:\n左组:{strLeft}\n右组:{strRight}"; + accessory.DebugMsg(str, DebugMode); + } + } + + #endregion P3.2 地火 + + #endregion P3 盖娅 + + #region P4 光暗巫女 + + [ScriptMethod(name: "---- 《P4:光暗巫女》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["ActionId:Hello1aya2World"], + userControl: true)] + public void SplitLine_Girls(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "未来的碎片受击警察", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(40(190|271|274|241|279|280|277|284|285|289|248|303|250))$"], + userControl: true)] + public void FragmentMonitor(Event ev, ScriptAccessory sa) + { + if (_fruPhase is not FruPhase.P4A_DarklitDragonsong and not FruPhase.P4B_CrystallizeTime) return; + + IGameObject? tObj = sa.GetById(ev.TargetId); + if (tObj == null) return; + + lock (_fragTargets) + { + // 初始化受击列表,并加入 + var tidx = ev.TargetIndex(); + if (tidx == 1 && _fragTargets.Count != 0) + _fragTargets.Clear(); + _fragTargets.Add(ev.TargetId); + + // 受击单位为未来的碎片 + var tDataId = tObj.DataId; + if (tDataId != FragmentDataId) return; + + // 获得导致碎片受击的技能 + var aid = ev.ActionId; + // 技能列表 + var skillParam = new Dictionary + { + { 40190, "光之波动引导" }, + { 40271, "水分摊" }, + { 40274, "暗分散" }, + { 40241, "撞龙头" }, + { 40279, "冰月环" }, + { 40280, "风击退" }, + { 40277, "暗分摊" }, + { 40284, "真夜舞蹈(远)" }, + { 40285, "真夜舞蹈(近)" }, + { 40289, "碎灵一击" }, + { 40248, "死亡轮回(希瓦)" }, + { 40303, "死亡轮回(盖娅)" }, + { 40250, "无尽顿悟" }, + }; + + var str = ""; + if (_fragTargets.Count != 1) + { + foreach (var target in _fragTargets) + { + IGameObject? obj = sa.GetById(target); + if (obj == null) continue; + str += obj.Name + " "; + } + } + + if (skillParam.TryGetValue(aid, out var skillName)) + { + sa.Method.SendChat($"/e 未来的碎片 似乎因【{str}】被【{skillName}】打中了!!!"); + } + } + } + + #region P4.1 光暗龙诗 + + [ScriptMethod(name: "光暗龙诗阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40239)$"], + userControl: Debugging)] + public void DarklitDragonsongPhaseChange(Event ev, ScriptAccessory sa) + { + _fruPhase = FruPhase.P4A_DarklitDragonsong; + _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + _recorded = new bool[20].ToList(); + sa.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + [ScriptMethod(name: "光之锁突出显示", eventType: EventTypeEnum.Tether, eventCondition: ["Id:006E"], + userControl: true)] + public void DldLightChainVisual(Event ev, ScriptAccessory sa) + { + if (_fruPhase != FruPhase.P4A_DarklitDragonsong) return; + var dp1 = sa.DrawGuidance(ev.SourceId, ev.TargetId, 0, 9000, $"Usami-光锁外"); + dp1.Color = ColorHelper.ColorDark.V4.WithW(3f); + var dp2 = sa.DrawGuidance(ev.SourceId, ev.TargetId, 0, 9000, $"Usami-光锁内"); + dp2.Color = ColorHelper.ColorYellow.V4; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp1); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp2); + } + + #endregion P4.1 光暗龙诗 + + #region P4.2 时间结晶 + + [ScriptMethod(name: "时间结晶阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40240)$"], + userControl: Debugging)] + public void CrystallizeTimePhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P4B_CrystallizeTime; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + #endregion P4.2 时间结晶 + + #endregion P4 光暗巫女 + + #region P5 潘多拉米特隆 + + [ScriptMethod(name: "---- 《P5:潘多拉·米特隆》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["ActionId:Hello1aya2World"], + userControl: true)] + public void SplitLine_Pandora(Event @event, ScriptAccessory accessory) + { + } + + #region P5.1 光尘之剑 + + [ScriptMethod(name: "光尘之剑阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40306)$"], + userControl: Debugging)] + public void FulgentBladePhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P5A_FulgentBlade; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + #endregion P5.1 光尘之剑 + + #region P5.2 复乐园 + + [ScriptMethod(name: "复乐园阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40319)$"], + userControl: Debugging)] + public void ParadiseRegainedPhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P5B_ParadiseRegained; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + #endregion P5.2 复乐园 + + #region P5.3 星灵之剑 + + [ScriptMethod(name: "星灵之剑阶段转换(DEBUG ONLY)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40316)$"], + userControl: Debugging)] + public void PolStrikePhaseChange(Event @event, ScriptAccessory accessory) + { + _fruPhase = FruPhase.P5C_PolarizingStrike; + accessory.DebugMsg($"当前阶段为:{_fruPhase}", DebugMode); + } + + #endregion P5.3 星灵之剑 + + #endregion P5 潘多拉米特隆 + + #region 事件枚举 + public enum EventIdx : int + { + // _events + ApoGrouping = 0, + ApoPreciseGrouping = 1, + + // _recorded + } + + #endregion + #region 类函数 + public class PriorityDict + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory accessory { 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) + { + accessory = _accessory; + Priorities = new Dictionary(); + for (var i = 0; i < partyNum; i++) + { + Priorities.Add(i, 0); + } + Annotation = annotation; + 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, 8, descending); + return sortedPriorities[idx]; + } + + /// + /// 从Priorities中找到对应key的数据,得到其Value排序后位置返回 + /// + /// + /// 降序排列,默认为false + /// + public int FindPriorityIndexOfKey(int key, bool descending = false) + { + var sortedPriorities = SelectMiddlePriorityIndices(0, 8, 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() + { + var str = $"{Annotation} 优先级字典:\n"; + foreach (var pair in Priorities) + { + str += $"Key {pair.Key} ({accessory.GetPlayerJobByIndex(pair.Key)}), Value {pair.Value}\n"; + } + return str; + } + + public string PrintAnnotation() + { + return Annotation; + } + + public PriorityDict DeepCopy() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)) ?? new PriorityDict(); + } + + public void AddActionCount(int count = 1) + { + ActionCount += count; + } + + public bool IsActionCountEqualTo(int times) + { + return ActionCount == times; + } + } + + public class Counter + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory accessory { get; set; } = null!; + public int Number { get; set; } = 0; + public bool Enable { get; set; } = true; + public string Annotation = ""; + + public void Init(ScriptAccessory _accessory, string annotation, bool enable = true) + { + accessory = _accessory; + Number = 0; + Enable = enable; + Annotation = annotation; + } + + public string ShowCounter() + { + var str = $"{Annotation} 计数器【{(Enable ? "使能" : "不使能")}】:{Number}\n"; + accessory.DebugMsg(str, DebugMode); + return str; + } + + public void DisableCounter() + { + Enable = false; + var str = $"禁止 {Annotation} 计数器的数值改变。\n"; + accessory.DebugMsg(str, DebugMode); + } + + public void EnableCounter() + { + Enable = true; + var str = $"使能 {Annotation} 计数器的数值改变。\n"; + accessory.DebugMsg(str, DebugMode); + } + + public void AddCounter(int num = 1) + { + if (!Enable) return; + Number += num; + } + + public void TimesCounter(int num = 1) + { + if (!Enable) return; + Number *= num; + } + } + + #endregion 类函数 + + #region 标点清除函数 + + private static void LocalMarkClear(ScriptAccessory accessory) + { + accessory.Method.Mark(0xE000000, MarkType.Attack1, true); + accessory.Method.Mark(0xE000000, MarkType.Attack2, true); + accessory.Method.Mark(0xE000000, MarkType.Attack3, true); + accessory.Method.Mark(0xE000000, MarkType.Attack4, true); + accessory.Method.Mark(0xE000000, MarkType.Attack5, true); + accessory.Method.Mark(0xE000000, MarkType.Attack6, true); + accessory.Method.Mark(0xE000000, MarkType.Attack7, true); + accessory.Method.Mark(0xE000000, MarkType.Attack8, true); + accessory.Method.Mark(0xE000000, MarkType.Bind1, true); + accessory.Method.Mark(0xE000000, MarkType.Bind2, true); + accessory.Method.Mark(0xE000000, MarkType.Bind3, true); + accessory.Method.Mark(0xE000000, MarkType.Stop1, true); + accessory.Method.Mark(0xE000000, MarkType.Stop2, true); + accessory.Method.Mark(0xE000000, MarkType.Square, true); + accessory.Method.Mark(0xE000000, MarkType.Circle, true); + accessory.Method.Mark(0xE000000, MarkType.Cross, true); + accessory.Method.Mark(0xE000000, MarkType.Triangle, true); + } + + private static void MarkClear(ScriptAccessory accessory) + { + if (!CaptainMode) return; + if (LocalTest) + { + accessory.DebugMsg($"本地测试删除标点。"); + if (LocalStrTest) return; + LocalMarkClear(accessory); + } + else + accessory.Method.MarkClear(); + } + + private static void MarkPlayerByIdx(ScriptAccessory accessory, int idx, MarkType marker, bool enable = true) + { + if (!CaptainMode) return; + if (!enable) return; + accessory.DebugMsg($"为{idx}({accessory.GetPlayerJobByIndex(idx)})标上{marker}。", DebugMode && LocalStrTest); + if (LocalStrTest) return; + accessory.Method.Mark(accessory.Data.PartyList[idx], marker, LocalTest); + } + + private static void MarkPlayerById(ScriptAccessory accessory, uint id, MarkType marker, bool enable = true) + { + if (!CaptainMode) return; + if (!enable) return; + accessory.DebugMsg($"为{accessory.GetPlayerIdIndex(id)}({accessory.GetPlayerJobById(id)})标上{marker}。", + DebugMode && LocalStrTest); + if (LocalStrTest) return; + accessory.Method.Mark(id, marker, LocalTest); + } + + private static int GetMarkedPlayerIndex(ScriptAccessory accessory, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } + + #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 DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Id2(this Event @event) + { + return ParseHexId(@event["Id2"], out var id) ? id : 0; + } + + public static uint Id0(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint TargetIndex(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetIndex"]); + } + + public static string Message(this Event ev) + { + return ev["Message"]; + } +} + +public static class IbcHelper +{ + public static IGameObject? GetById(this ScriptAccessory sa, ulong id) + { + return sa.Data.Objects.SearchById(id); + } + + 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 GetCharJob(this ScriptAccessory sa, uint id, bool fullName = false) + { + IPlayerCharacter? chara = (IPlayerCharacter?)sa.GetById(id); + if (chara == null) return "None"; + return fullName ? chara.ClassJob.Value.Name.ToString() : chara.ClassJob.Value.Abbreviation.ToString(); + } + + public static bool AtNorth(this ScriptAccessory sa, uint id, float centerZ) + { + var chara = sa.GetById(id); + if (chara == null) return false; + return chara.Position.Z <= centerZ; + } + public static bool AtSouth(this ScriptAccessory sa, uint id, float centerZ) + { + var chara = sa.GetById(id); + if (chara == null) return false; + return chara.Position.Z > centerZ; + } + public static bool AtWest(this ScriptAccessory sa, uint id, float centerX) + { + var chara = sa.GetById(id); + if (chara == null) return false; + return chara.Position.X <= centerX; + } + public static bool AtEast(this ScriptAccessory sa, uint id, float centerX) + { + var chara = sa.GetById(id); + if (chara == null) return false; + return chara.Position.X > centerX; + } + + public static bool HasStatus(this ScriptAccessory sa, uint id, uint statusId) + { + IGameObject? chara = sa.GetById(id); + if (chara == null || !chara.IsValid()) return false; + unsafe + { + BattleChara* charaStruct = (BattleChara*)chara.Address; + return charaStruct->GetStatusManager()->HasStatus(statusId, id); + } + } +} + +public static class DirectionCalc +{ + 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建立list + // Game List Logic + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Logic = Pi - Game (+ 2pi) + + /// + /// 将游戏基角度(以南为0,逆时针增加)转为逻辑基角度(以北为0,顺时针增加) + /// 算法与Logic2Game完全相同,但为了代码可读性,便于区分。 + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float Game2Logic(this float radian) + { + // if (r < 0) r = (float)(r + 2 * Math.PI); + // if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + + var r = float.Pi - radian; + r = (r + float.Pi * 2) % (float.Pi * 2); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// 算法与Game2Logic完全相同,但为了代码可读性,便于区分。 + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float Logic2Game(this float radian) + { + // var r = (float)Math.PI - radian; + // if (r < Math.PI) r = (float)(r + 2 * Math.PI); + // if (r > Math.PI) r = (float)(r - 2 * Math.PI); + + return radian.Game2Logic(); + } + + /// + /// 适用于旋转,FF14游戏基顺时针旋转为负。 + /// + /// + /// + public static float Cw2Ccw(this float radian) + { + return -radian; + } + + /// + /// 适用于旋转,FF14游戏基顺时针旋转为负。 + /// 与Cw2CCw完全相同,为了代码可读性便于区分。 + /// + /// + /// + public static float Ccw2Cw(this float radian) + { + return -radian; + } + + /// + /// 输入逻辑基角度,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 逻辑基角度 + /// 方位总数 + /// 斜分割,默认true + /// 逻辑基角度对应的逻辑方位 + public static int Rad2Dirs(this float radian, int dirs, bool diagDivision = true) + { + var r = diagDivision + ? Math.Round(radian / (2f * float.Pi / dirs)) + : Math.Floor(radian / (2f * float.Pi / dirs)); + r = (r + dirs) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 斜分割,默认true + /// 该坐标点对应的逻辑方位 + public static int Position2Dirs(this Vector3 point, Vector3 center, int dirs, bool diagDivision = true) + { + double dirsDouble = dirs; + var r = diagDivision + ? Math.Round(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble + : Math.Floor(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble; + return (int)r; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(this Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new Vector3(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(this Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new Vector3(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(this Vector3 newPoint, Vector3 center) + { + var radian = MathF.PI - MathF.Atan2(newPoint.X - center.X, newPoint.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointHorizon(this Vector3 point, float centerX) + { + return point with { X = 2 * centerX - point.X }; + } + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointVertical(this Vector3 point, float centerZ) + { + return point with { Z = 2 * centerZ - point.Z }; + } + + /// + /// 将输入点中心对称 + /// + /// 输入点 + /// 中心点 + /// + public static Vector3 PointCenterSymmetry(this Vector3 point, Vector3 center) + { + return point.RotatePoint(center, float.Pi); + } + + /// + /// 将输入点朝某中心点往内/外同角度延伸,默认向内 + /// + /// 待延伸点 + /// 中心点 + /// 延伸长度 + /// 是否向外延伸> + /// + public static Vector3 PointInOutside(this Vector3 point, Vector3 center, float length, bool isOutside = false) + { + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var targetPos = (point - center) / v2.Length() * length * (isOutside ? 1 : -1) + point; + return targetPos; + } + + /// + /// 获得两点之间距离 + /// + /// + /// + /// + public static float DistanceTo(this Vector3 point, Vector3 target) + { + Vector2 v2 = new(point.X - target.X, point.Z - target.Z); + return v2.Length(); + } + + /// + /// 寻找两点之间的角度差,范围0~360deg + /// + /// 基准位置 + /// 比较目标位置 + /// 场地中心 + /// + public static float FindRadianDifference(this Vector3 targetPos, Vector3 basePoint, Vector3 center) + { + var baseRad = basePoint.FindRadian(center); + var targetRad = targetPos.FindRadian(center); + var deltaRad = targetRad - baseRad; + if (deltaRad < 0) + deltaRad += float.Pi * 2; + return deltaRad; + } + + /// + /// 从第三人称视角出发观察某目标是否在另一目标的右侧。 + /// + /// 基准位置 + /// 比较目标位置 + /// 场地中心 + /// + public static bool IsAtRight(this Vector3 targetPos, Vector3 basePoint, Vector3 center) + { + // 从场中看向场外,在右侧 + return targetPos.FindRadianDifference(basePoint, center) < float.Pi; + } + + /// + /// 获取给定数的指定位数 + /// + /// 给定数值 + /// 对应位数,个位为1 + /// + public static int GetDecimalDigit(this int val, int x) + { + string valStr = val.ToString(); + int length = valStr.Length; + + if (x < 1 || x > length) + { + return -1; + } + + char digitChar = valStr[length - x]; // 从右往左取第x位 + return int.Parse(digitChar.ToString()); + } +} + +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory accessory, ulong pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf((uint)pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory accessory, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = accessory.Data.PartyList.IndexOf(pid); + var str = accessory.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory accessory, int idx, bool fourPeople = false) + { + var str = idx switch + { + 0 => "MT", + 1 => fourPeople ? "H1" : "ST", + 2 => fourPeople ? "D1" : "H1", + 3 => fourPeople ? "D2" : "H2", + 4 => "D1", + 5 => "D2", + 6 => "D3", + 7 => "D4", + _ => "unknown" + }; + return str; + } +} + +public static class ColorHelper +{ + public static ScriptColor ColorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor ColorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + public static ScriptColor ColorDark = new ScriptColor { V4 = new Vector4(0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorLightBlue = new ScriptColor { V4 = new Vector4(0.48f, 0.40f, 0.93f, 1.0f) }; + public static ScriptColor ColorWhite = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; + public static ScriptColor ColorYellow = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + +} + +#region 绘图函数 +public static class AssignDp +{ + /// + /// 返回箭头指引相关dp + /// + /// + /// 箭头起始,可输入uint或Vector3 + /// 箭头指向目标,可输入uint或Vector3,为0则无目标 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 箭头旋转角度 + /// 箭头宽度 + /// 使用安全色 + /// + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f, bool isSafe = true) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Rotation = rotation; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = isSafe ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + + switch (ownerObj) + { + case uint sid: + dp.Owner = sid; + break; + case ulong sid: + dp.Owner = sid; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException("ownerObj的目标类型输入错误"); + } + + switch (targetObj) + { + case uint tid: + if (tid != 0) dp.TargetObject = tid; + break; + case ulong tid: + if (tid != 0) dp.TargetObject = tid; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + } + + return dp; + } + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f, bool isSafe = true) + => accessory.DrawGuidance(accessory.Data.Me, targetObj, delay, destroy, name, rotation, scale, isSafe); + + // { + // return targetObj switch + // { + // uint uintTarget => accessory.DrawGuidance(accessory.Data.Me, uintTarget, delay, destroy, name, rotation, scale), + // Vector3 vectorTarget => accessory.DrawGuidance(accessory.Data.Me, vectorTarget, delay, destroy, name, rotation, scale), + // _ => throw new ArgumentException("targetObj 的类型必须是 uint 或 Vector3") + // }; + // } + + /// + /// 返回扇形左右刀 + /// + /// + /// 起始目标id,通常为boss + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 是左刀 + /// 扇形角度 + /// 扇形尺寸 + /// 动画效果随时间填充 + /// + public static DrawPropertiesEdit DrawLeftRightCleave(this ScriptAccessory accessory, uint ownerId, bool isLeftCleave, int delay, int destroy, string name, float radian = float.Pi, float scale = 60f, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Radian = radian; + dp.Rotation = isLeftCleave ? float.Pi / 2 : -float.Pi / 2; + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回扇形前后刀 + /// + /// + /// 起始目标id,通常为boss + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 是前刀 + /// 扇形角度 + /// 扇形尺寸 + /// 动画效果随时间填充 + /// + public static DrawPropertiesEdit DrawFrontBackCleave(this ScriptAccessory accessory, uint ownerId, bool isFrontCleave, int delay, int destroy, string name, float radian = float.Pi, float scale = 60f, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Radian = radian; + dp.Rotation = isFrontCleave ? 0 : -float.Pi; + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回距离某对象目标最近/最远的dp + /// + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图宽度 + /// 绘图长度 + /// true为最近,false为最远 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// + public static DrawPropertiesEdit DrawTargetNearFarOrder(this ScriptAccessory accessory, ulong ownerId, uint orderIdx, + bool isNear, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = + isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回距离某坐标位置最近/最远的dp + /// + /// + /// 特定坐标点 + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图宽度 + /// 绘图长度 + /// true为最近,false为最远 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// + public static DrawPropertiesEdit DrawPositionNearFarOrder(this ScriptAccessory accessory, Vector3 position, uint orderIdx, + bool isNear, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Position = position; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.TargetResolvePattern = + isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp.TargetOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回ownerId施法目标的dp + /// + /// + /// 起始目标id,通常为boss + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawOwnersTarget(this ScriptAccessory accessory, uint ownerId, float width, float length, int delay, + int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回ownerId仇恨相关的dp + /// + /// + /// 起始目标id,通常为boss + /// 仇恨顺序,从1开始 + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawOwnersEnmityOrder(this ScriptAccessory accessory, uint ownerId, uint orderIdx, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回owner与target的dp,可修改 dp.Owner, dp.TargetObject, dp.Scale + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 绘图旋转角度 + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 长度是否随距离改变 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawTarget2Target(this ScriptAccessory accessory, uint ownerId, uint targetId, float width, float length, int delay, int destroy, string name, float rotation = 0, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Rotation = rotation; + dp.Owner = ownerId; + dp.TargetObject = targetId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回画向某目标的扇形绘图 + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 扇形角度 + /// 扇形尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图颜色 + /// 旋转角度 + /// 长度是否随距离改变 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawFanToTarget(this ScriptAccessory accessory, uint sourceId, uint targetId, float radian, float scale, int delay, int destroy, string name, Vector4 color, float rotation = 0, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.DrawTarget2Target(sourceId, targetId, scale, scale, delay, destroy, name, rotation, lengthByDistance, byTime); + dp.Radian = radian; + dp.Color = color; + return dp; + } + + /// + /// 返回owner与target之间的连线dp,使用Line绘制 + /// + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 线条宽度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawConnectionBetweenTargets(this ScriptAccessory accessory, uint ownerId, + uint targetId, int delay, int destroy, string name, float scale = 1f) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Owner = ownerId; + dp.TargetObject = targetId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= ScaleMode.YByDistance; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 圆圈尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory accessory, ulong ownerId, float scale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回环形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 外环实心尺寸 + /// 内环空心尺寸 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory accessory, uint ownerId, float scale, float innerScale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.DrawFan(ownerId, float.Pi * 2, 0, scale, innerScale, delay, destroy, name, byTime); + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 绘图起始,可输入uint或Vector3 + /// 绘图目标,可输入uint或Vector3,为0则无目标 + /// 图形角度 + /// 旋转角度,以北为0度顺时针 + /// 绘图宽度 + /// 绘图长度 + /// 是Vector4则选用该颜色 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit DrawStatic(this ScriptAccessory accessory, object ownerObj, object targetObj, + float radian, float rotation, float width, float length, object color, int delay, int destroy, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + switch (ownerObj) + { + case uint sid: + dp.Owner = sid; + break; + case ulong sid: + dp.Owner = sid; + break; + case Vector3 spos: + dp.Position = spos; + break; + } + switch (targetObj) + { + case uint tid: + if (tid != 0) dp.TargetObject = tid; + break; + case ulong tid: + if (tid != 0) dp.TargetObject = tid; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + } + dp.Radian = radian; + dp.Rotation = rotation.Logic2Game(); + switch (color) + { + case Vector4 clr: + dp.Color = clr; + break; + default: + dp.Color = accessory.Data.DefaultDangerColor; + break; + } + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + /// + /// 返回静态圆圈dp,通常用于指引固定位置。 + /// + /// + /// 圆圈中心位置 + /// 圆圈颜色 + /// 圆圈尺寸,默认1.5f + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawStaticCircle(this ScriptAccessory accessory, Vector3 center, Vector4 color, + int delay, int destroy, string name, float scale = 1.5f) + => accessory.DrawStatic(center, (uint)0, 0, 0, scale, scale, color, delay, destroy, name); + // { + // var dp = accessory.DrawStatic(center, (uint)0, 0, 0, scale, scale, color, delay, destroy, name); + // return dp; + // } + + /// + /// 返回静态月环dp,通常用于指引固定位置。 + /// + /// + /// 月环中心位置 + /// 月环颜色 + /// 月环外径,默认1.5f + /// 月环内径,默认scale-0.05f + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawStaticDonut(this ScriptAccessory accessory, Vector3 center, Vector4 color, + int delay, int destroy, string name, float scale, float innerscale = 0) + => accessory.DrawStatic(center, (uint)0, + float.Pi * 2, 0, scale, scale, color, delay, destroy, name); + + // { + // var dp = accessory.DrawStatic(center, (uint)0, float.Pi * 2, 0, scale, scale, color, delay, destroy, name); + // dp.InnerScale = innerscale != 0f ? new Vector2(innerscale) : new Vector2(scale - 0.05f); + // return dp; + // } + + /// + /// 返回矩形 + /// + /// 起始目标id,通常为自己或Boss + /// 矩形宽度 + /// 矩形长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory accessory, uint ownerId, float width, float length, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回扇形 + /// + /// 起始目标id,通常为自己或Boss + /// 扇形弧度 + /// 图形旋转角度 + /// 扇形尺寸 + /// 扇形内环空心尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory accessory, uint ownerId, float radian, float rotation, float scale, float innerScale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.InnerScale = new Vector2(innerScale); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回击退 + /// + /// + /// 击退源,可输入uint或Vector3 + /// 击退绘图宽度 + /// 击退绘图长度/距离 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 起始目标ID,通常为自己或其他玩家 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory accessory, uint ownerId, object target, float length, int delay, int destroy, string name, float width = 1.5f, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + switch (target) + { + // 根据传入的 tid 类型来决定是使用 TargetObject 还是 TargetPosition + case uint tid: + dp.TargetObject = tid; // 如果 tid 是 uint 类型 + break; + case ulong tid: + dp.TargetObject = tid; // 如果 tid 是 ulong 类型 + break; + case Vector3 tpos: + dp.TargetPosition = tpos; // 如果 tid 是 Vector3 类型 + break; + default: + throw new ArgumentException("DrawKnockBack的目标类型输入错误"); + } + dp.Rotation = float.Pi; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory accessory, object target, float length, + int delay, int destroy, string name, float width = 1.5f, bool byTime = false) + => accessory.DrawKnockBack(accessory.Data.Me, target, length, delay, destroy, name, width, byTime); + // { + // return target switch + // { + // uint uintTarget => accessory.DrawKnockBack(accessory.Data.Me, uintTarget, length, delay, destroy, name, width, byTime), + // Vector3 vectorTarget => accessory.DrawKnockBack(accessory.Data.Me, vectorTarget, length, delay, destroy, name, width, byTime), + // _ => throw new ArgumentException("target 的类型必须是 uint 或 Vector3") + // }; + // } + + /// + /// 返回背对 + /// + /// + /// 背对源,可输入uint或Vector3 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 起始目标ID,通常为自己或其他玩家 + /// + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory accessory, uint ownerId, object target, int delay, int destroy, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = ownerId; + switch (target) + { + // 根据传入的 tid 类型来决定是使用 TargetObject 还是 TargetPosition + case uint tid: + dp.TargetObject = tid; // 如果 tid 是 uint 类型 + break; + case ulong tid: + dp.TargetObject = tid; // 如果 tid 是 ulong 类型 + break; + case Vector3 tpos: + dp.TargetPosition = tpos; // 如果 tid 是 Vector3 类型 + break; + default: + throw new ArgumentException("DrawSightAvoid的目标类型输入错误"); + } + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory accessory, object target, int delay, + int destroy, string name) + => accessory.DrawSightAvoid(accessory.Data.Me, target, delay, destroy, name); + // { + // return target switch + // { + // uint uintTarget => accessory.DrawSightAvoid(accessory.Data.Me, uintTarget, delay, destroy, name), + // Vector3 vectorTarget => accessory.DrawSightAvoid(accessory.Data.Me, vectorTarget, delay, destroy, name), + // _ => throw new ArgumentException("target 的类型必须是 uint 或 Vector3") + // }; + // } + + /// + /// 返回多方向延伸指引 + /// + /// + /// 分散源 + /// 分散角度 + /// 玩家对应角度idx + /// 指引箭头宽度 + /// 指引箭头长度 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 玩家对应箭头指引颜色 + /// 其他玩家对应箭头指引颜色 + /// + /// + public static List DrawExtendDirection(this ScriptAccessory accessory, object owner, + List extendDirs, int myDirIdx, float width, float length, int delay, int destroy, string name, + Vector4 colorPlayer, Vector4 colorNormal) + { + List dpList = []; + switch (owner) + { + case uint sid: + for (var i = 0; i < extendDirs.Count; i++) + { + var dp = accessory.DrawGuidance(owner, sid, delay, destroy, $"{name}{i}", extendDirs[i], width); + dp.Color = i == myDirIdx ? colorPlayer : colorNormal; + dpList.Add(dp); + } + break; + case Vector3 spos: + for (var i = 0; i < extendDirs.Count; i++) + { + var dp = accessory.DrawGuidance(spos, spos.ExtendPoint(extendDirs[i], length), delay, destroy, + $"{name}{i}", 0, width); + dp.Color = i == myDirIdx ? colorPlayer : colorNormal; + dpList.Add(dp); + } + break; + default: + throw new ArgumentException("DrawExtendDirection的目标类型输入错误"); + } + + return dpList; + } + + /// + /// 返回多地点指路指引列表 + /// + /// + /// 地点位置 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 对应位置标记行动颜色 + /// 对应位置标记准备颜色 + /// 指路出发箭头颜色 + /// 指路准备箭头颜色 + /// dpList中的三个List:位置标记,玩家指路箭头,地点至下个地点的指路箭头 + public static List> DrawMultiGuidance(this ScriptAccessory accessory, + List positions, List delay, List destroy, string name, + Vector4 colorGo, Vector4 colorPrepare, Vector4 colorPosNormal, Vector4 colorPosPlayer) + { + List> dpList = [[], [], []]; + for (var i = 0; i < positions.Count; i++) + { + var dpPos = accessory.DrawStaticCircle(positions[i], colorPosPlayer, delay[i], destroy[i], $"{name}pos{i}"); + dpList[0].Add(dpPos); + var dpGuide = accessory.DrawGuidance(positions[i], colorGo, delay[i], destroy[i], $"{name}guide{i}"); + dpList[1].Add(dpGuide); + if (i == positions.Count - 1) break; + var dpPrep = accessory.DrawGuidance(positions[i], positions[i + 1], delay[i], destroy[i], $"{name}prep{i}"); + dpList[2].Add(dpPrep); + } + return dpList; + } + + public static void DebugMsg(this ScriptAccessory accessory, string str, bool debugMode = false, bool debugChat = false) + { + if (!debugMode) + return; + accessory.Log.Debug($"/e [DEBUG] {str}"); + + if (!debugChat) + return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory accessory, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return accessory.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} + +#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 SetRotation(this ScriptAccessory sa, IGameObject? obj, float rotation) + { + if (obj == null || !obj.IsValid()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + unsafe + { + GameObject* charaStruct = (GameObject*)obj.Address; + charaStruct->SetRotation(rotation); + } + sa.Log.Debug($"SetRotation => {obj.Name.TextValue} | {obj} => {rotation}"); + } +} + +#endregion 特殊函数 + +#endregion 函数集 diff --git a/lianying1997/07-Dawntrail/M6S/M6S.cs b/lianying1997/07-Dawntrail/M6S/M6S.cs new file mode 100644 index 0000000..bc0ada7 --- /dev/null +++ b/lianying1997/07-Dawntrail/M6S/M6S.cs @@ -0,0 +1,2160 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; + +namespace UsamisKodakku.Script._07_DawnTrail.M6S; + +[ScriptType(name: Name, territorys: [1259], guid: "4f5decb5-4a78-4e83-96c8-09678761a751", + version: Version, author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class M6S +{ + const string NoteStr = + """ + v0.0.0.15 + 请先于用户设置中调整整体策略(Game8/CnServer)。 + 待完善。 + """; + + private const string Name = "M6S [零式阿卡狄亚 中量级2]"; + private const string Version = "0.0.0.15"; + + private const string UpdateInfo = + """ + 1. 适配鸭鸭0.5.x.x + """; + + private const bool Debugging = false; + + private static List _role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + + [UserSetting("整体策略")] + public static StgEnum GlobalStrat { get; set; } = StgEnum.CnServer; + public enum StgEnum + { + Game8, + CnServer, + } + + [UserSetting("仙人掌四角策略")] + public static QuickSandStgEnum QuickSandStrat { get; set; } = QuickSandStgEnum.BattleFieldBasis_场基; + public enum QuickSandStgEnum + { + BattleFieldBasis_场基, + DangerCornerBasis_危险基, + } + + private enum M6SPhase + { + Init, + P1A_DoubleStyle, // P1A 双画画 + P2A_Sand, // P2A 沙漠 + P2B_FlyingBomb, // P2B 沙漠飞行炸弹 + P3A_Mobs, // P3A 小怪 + P4A_Bridge, // P4A 桥 - 火雷 + P4B_Light, // P4B 桥 - 雷云 + P4C_Lava, // P4C 桥 - 岩浆 + P4D_FlyLava, // P4D 桥 - 岩浆飞行 + P5A_Final // P5A 终 + } + + private static readonly Vector3 Center = new Vector3(100, 0, 100); + private static readonly Vector3 CenterRiver = new Vector3(100, 0, 96); + private M6SPhase _M6sPhase = M6SPhase.Init; + private static readonly Random Random = new(); + private volatile List _bools = new bool[20].ToList(); + private List _numbers = Enumerable.Repeat(0, 20).ToList(); + private static List _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + + // 岛:右上、下、左上 + private static readonly List IslandPos = [new(112, 0, 90), new(97, 0, 113), new(89, 0, 89)]; + // 桥:云在右上,云在下,云在左上(左下,上,右下) + private static readonly List BridgePos = [new(91.5f, 0, 103f), new(100.76f, 0, 91f), new(107.1f, 0, 105.4f)]; + // 左上岛的塔:69~78 10 座 + // 以面向场中为基准,70 71 73 75 77 靠左,69 72 74 76 78 靠右 + // 右上岛的塔:79~88 10 座 + // 以面向场中未基准,80 82 84 86 88 靠左,79 81 83 85 87 靠右 + // 正下岛的塔:89~96 8 座 + // 南八塔中,89MT 90D3 91ST 92D4 93H1 94D1 95H2 96D2(国服) + // 南八塔中,89D3 90H1 91MT 92D2 93H2 94D1 95D4 96ST(Game8) + private static readonly List TowerPosList = + [ + new(83, 0, 91), new(93, 0, 89), new(92, 0, 96), new(83, 0, 102), new(94, 0, 84), + new(83, 0, 88), new(90, 0, 89), new(83, 0, 95), new(90, 0, 97.5f), new(83, 0, 104), + + new(110, 0, 93), new(117, 0, 92), new(109, 0, 97), new(115, 0, 105), new(110, 0, 83), + new(117, 0, 85), new(110, 0, 91), new(117, 0, 96), new(111, 0, 100), new(117, 0, 106), + + new(100, 0, 108), new(85, 0, 114), new(98, 0, 107), new(112, 0, 106), + new(92, 0, 110), new(91, 0, 117), new(107, 0, 111), new(105, 0, 117) + ]; + + private static Towers _towers = new(); + private static PriorityDict _pd = new(); + private static bool _initHint = false; + + public void Init(ScriptAccessory sa) + { + _M6sPhase = M6SPhase.Init; + InitParams(); + sa.Log.Debug($"M6S {Version} 脚本已刷新。"); + sa.Method.RemoveDraw(".*"); + _initHint = false; + } + + private void InitParams() + { + _bools = new bool[20].ToList(); + _numbers = Enumerable.Repeat(0, 20).ToList(); + _events = Enumerable + .Range(0, 20) + .Select(_ => new ManualResetEvent(false)) + .ToList(); + } + + #region 测试项 + + [ScriptMethod(name: "---- 测试项 ----", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: Debugging)] + public void Debug_SplitLine(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "测试项 雷火位置", eventType: EventTypeEnum.Chat, + eventCondition: ["HelloayaWorld:asdf"], userControl: Debugging)] + public void Debug_StackOrSpread(Event ev, ScriptAccessory sa) + { + List points = + [ + // 左上就位:MT/H1/D1/D2分摊,ST/H2/D3/D4分摊 + // new(98.5f, 0, 98.5f), new(85f, 0, 108f), + // // 右下就位:MT/H1/D1/D2分摊,ST/H2/D3/D4分摊 + // new(101.5f, 0, 101.5f), new(87.5f, 0, 105.5f), + // // 左上就位:MT/ST/H1/H2/D1/D2/D3/D4分散, + // new(96.5f, 0, 83), new(96.5f, 0, 110.5f), new(83f, 0, 96.5f), new(110.5f, 0, 96.5f), + // new(84.8f, 0, 112.7f), new(108.3f, 0, 112.6f), new(83f, 0, 83f), new(110.5f, 0, 83f), + // // 右下就位:MT/ST/H1/H2/D1/D2/D3/D4分散, + new(103f, 0, 91.5f), new(103f, 0, 117f), new(90.5f, 0, 103.65f), new(117f, 0, 103.5f), + new(89.5f, 0, 117f), new(116.8f, 0, 116.8f), new(89.5f, 0, 89.5f), new(117f, 0, 90f), + ]; + foreach (var point in points) + { + var dp = sa.DrawStaticCircle(point, sa.Data.DefaultSafeColor, 0, 2000, "a"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + } + + [ScriptMethod(name: "测试项 获得雷云位置与面向", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: Debugging)] + public void Debug_CloudPosAndRotation(Event ev, ScriptAccessory sa) + { + GetCloudInfo(sa); + } + + [ScriptMethod(name: "测试项 计算角度与面向", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: Debugging)] + public void Debug_CalcDir(Event ev, ScriptAccessory sa) + { + var rad = 96f.DegToRad(); + var cloudRotDir = rad.Rad2Dirs(6, false); + sa.Log.Debug($"{cloudRotDir}"); + } + + [ScriptMethod(name: "测试项 塔范围", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: Debugging)] + public void Debug_TowerRegion(Event ev, ScriptAccessory sa) + { + var dp = sa.DrawStaticCircle(TowerPosList[0], sa.Data.DefaultSafeColor, 0, 2000, "a", 3f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "测试项 找到主视角塔", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: Debugging)] + public void Debug_DrawMyTower(Event ev, ScriptAccessory sa) + { + var tower = _towers.GetTowerByPlayerIdx(sa.GetMyIndex()); + sa.Log.Debug($"玩家 {sa.GetPlayerJobByIndex(sa.GetMyIndex())} 的塔为 {tower.TowerIdx}, 在岛 {tower.IslandIdx}"); + var dp = sa.DrawStaticCircle(tower.TowerPos, sa.Data.DefaultSafeColor, 0, 2000, "a", 3f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "测试项 四象限安全区", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: Debugging)] + public void Debug_DrawSafeFlyPoint(Event ev, ScriptAccessory sa) + { + List safePosList = Enumerable.Repeat(new Vector3(0, 0, 0), 20).ToList(); + + // 飞行位置 + safePosList[2] = new Vector3(112, 0, 88); + safePosList[0] = safePosList[2].RotatePoint(Center, 180f.DegToRad()); + safePosList[1] = safePosList[2].RotatePoint(Center, 270f.DegToRad()); + safePosList[3] = safePosList[2].RotatePoint(Center, 90f.DegToRad()); + + safePosList[4] = new Vector3(119, 0, 92); // 第0象限 左 + safePosList[5] = new Vector3(108, 0, 81); // 第0象限 右 + safePosList[6] = new Vector3(112, 0, 88); // 第0象限 前 + safePosList[7] = new Vector3(118, 0, 82); // 第0象限 后 + + safePosList[8] = safePosList[5].FoldPointVertical(Center.Z); // 第1象限 左 + safePosList[9] = safePosList[4].FoldPointVertical(Center.Z); // 第1象限 右 + safePosList[10] = safePosList[6].FoldPointVertical(Center.Z); // 第1象限 前 + safePosList[11] = safePosList[7].FoldPointVertical(Center.Z); // 第1象限 后 + + safePosList[12] = safePosList[4].PointCenterSymmetry(Center); // 第2象限 左 + safePosList[13] = safePosList[5].PointCenterSymmetry(Center); // 第2象限 右 + safePosList[14] = safePosList[6].PointCenterSymmetry(Center); // 第2象限 前 + safePosList[15] = safePosList[7].PointCenterSymmetry(Center); // 第2象限 后 + + safePosList[16] = safePosList[5].FoldPointHorizon(Center.X); // 第3象限 左 + safePosList[17] = safePosList[4].FoldPointHorizon(Center.X); // 第3象限 右 + safePosList[18] = safePosList[6].FoldPointHorizon(Center.X); // 第3象限 前 + safePosList[19] = safePosList[7].FoldPointHorizon(Center.X); // 第3象限 后 + + for (int i = 0; i < 20; i++) + { + var dp = sa.DrawStaticCircle(safePosList[i], sa.Data.DefaultSafeColor, 1000*i, 1000, "a", 1.5f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + + } + + #endregion 测试项 + + #region P1 开场 + + [ScriptMethod(name: "---- P1 开场 ----", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: true)] + public void P1_SplitLine(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "策略与身份提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(42684)$"], userControl: true)] + public void 策略与身份提示(Event ev, ScriptAccessory sa) + { + if (_initHint) return; + _initHint = true; + var myIndex = sa.Data.PartyList.IndexOf(sa.Data.Me); + List role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + sa.Method.TextInfo( + $"你是【{role[myIndex]}】,使用策略为【{(GlobalStrat == StgEnum.CnServer ? "国服" : "日野")}】,\n" + + $"沙漠阶段四角仙人掌采用【{(QuickSandStrat == QuickSandStgEnum.BattleFieldBasis_场基 ? "场基" : "危险基")}】,\n" + + $"若有误请及时调整。", 5000); + } + + [ScriptMethod(name: "WingMark引起的阶段转换", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42614"], userControl: Debugging)] + public void P1A_PhaseChange(Event ev, ScriptAccessory sa) + { + if (_M6sPhase == M6SPhase.P4C_Lava) + { + _M6sPhase = M6SPhase.P4D_FlyLava; + _towers.RefreshParam(sa, true); + } + else + { + if (_M6sPhase != M6SPhase.Init & _M6sPhase != M6SPhase.P4D_FlyLava) return; + _M6sPhase = M6SPhase.P1A_DoubleStyle; + } + + InitParams(); + sa.Log.Debug($"当前阶段为:{_M6sPhase}"); + } + + [ScriptMethod(name: "P1A 色彩狂热 死刑", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(4264[12])$"], userControl: true)] + public void P1A_ColorRiot(Event ev, ScriptAccessory sa) + { + if (sa.Data.PartyList.Count != 8) return; + var myIndex = sa.GetMyIndex(); + if (myIndex > 1) return; + + // const uint blueClose = 42641; + const uint redClose = 42642; + const uint redBuff = 4451; + const uint blueBuff = 4452; + + var aid = ev.ActionId; + var flag = -1 * myIndex; + // MT=0, ST=1, MT先近 + // 规定flag>0时,近;flag<0时,远 + + // 判断自身 + var myObj = sa.Data.MyObject; + if (myObj == null) return; + var partnerObj = (IPlayerCharacter?)sa.GetById(sa.Data.PartyList[myIndex == 0 ? 1 : 0]); + if (partnerObj == null) return; + + // 令红Buff>0,同时考虑玩家自身与搭档。另外,若出现双同色Buff情况,让玩家的命更值钱,权重上升。 + flag += (myObj.HasStatus(redBuff) ? 1 : 0) * 10 + (myObj.HasStatus(blueBuff) ? -1 : 0) * 10; + flag += (partnerObj.HasStatus(redBuff) ? 1 : 0) * 5 + (partnerObj.HasStatus(blueBuff) ? -1 : 0) * 5; + flag *= aid == redClose ? -1 : 1; // redClose是指Boss会赋于红Buff,需要红Buff远离 + sa.Log.Debug($"死刑 Flag: {flag}"); + + sa.Method.TextInfo(flag >= 0 ? $"【靠近】死刑" : $"【远离】死刑", 5000, true); + } + + [ScriptMethod(name: "---- P1A 黏黏慕斯怪初始化 ----", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42645"], userControl: Debugging)] + public void StickyMousseInit(Event ev, ScriptAccessory sa) + { + // Init Param at StartCasting 42645 + _pd.Init(sa, "StickyMousse"); + _pd.AddPriorities([0, 0, 6, 3, 5, 4, 7, 2]); + // D3 7 D4 2 + // H1 6 H2 3 + // D1 5 D2 4 + sa.Log.Debug($"检测到Sticky Mousse,优先级初始化"); + } + + [ScriptMethod(name: "P1A 黏黏慕斯怪", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:42646", "TargetIndex:1"], userControl: true)] + public void StickyMousseTarget(Event ev, ScriptAccessory sa) + { + // ActionEffect 42646, Index 1 + var tidx = sa.GetPlayerIdIndex(ev.TargetId); + int targetIdx = -1; + lock (_pd) + { + _pd.AddActionCount(); + _pd.AddPriority(tidx, 1000); + if (_pd.ActionCount != 2) return; + + var myIndex = sa.GetMyIndex(); + // 玩家是分摊目标,不执行后续 + if (_pd.Priorities[myIndex] > 1000) return; + + for (int i = 0; i < 2; i++) + { + var tKey = _pd.SelectSpecificPriorityIndex(i, true).Key; + var tVal = _pd.Priorities[tKey].GetDecimalDigit(1); + var myVal = _pd.Priorities[myIndex]; + var distance = 0; + // 计算距离 + if (myIndex != 0 & myIndex != 1) + { + distance = 3 - Math.Abs(Math.Abs(tVal - myVal) - 3); + _pd.AddPriority(tKey, distance * 100); + } + + // 计算顺逆 + var cwIdx = (tVal - myVal + 6 - (myIndex is 0 or 1 ? 2 : 0)) % 6; + _pd.AddPriority(tKey, cwIdx * 10); + sa.Log.Debug($"tKey: {tKey}, tVal: {tVal}, myVal: {myVal}"); + sa.Log.Debug($"玩家{sa.GetPlayerJobByIndex(myIndex)}与{sa.GetPlayerJobByIndex(tKey)}的距离为{distance},顺时针顺位为{cwIdx},对方优先值为{_pd.Priorities[tKey]}"); + } + + // MT找两个目标中较大的(降序idx0),ST与人群找较小的(降序idx1) + targetIdx = _pd.SelectSpecificPriorityIndex(myIndex == 0 ? 0 : 1, true).Key; + sa.Log.Debug( + $"据决策,玩家{sa.GetPlayerJobByIndex(myIndex)}的分摊对象为{sa.GetPlayerJobByIndex(targetIdx)}({_pd.Priorities[targetIdx]})"); + } + + var dp = sa.DrawGuidance((ulong)sa.Data.PartyList[targetIdx], 0, 4000, $"粘性炸弹目标"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "P1A 双手涂鸦初始化", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(4263[57])$"], userControl: Debugging)] + public void ColorClashStackRecord(Event ev, ScriptAccessory sa) + { + const uint partnerStack = 42637; + // const uint partyStack = 42635; + _pd.Init(sa, "飞行分摊", 5); + _pd.AddPriority(4, ev.ActionId == partnerStack ? 2 : 4); + // 前四个index留作方位 + sa.Log.Debug($"记录分摊人数:{_pd.Priorities[4]}人分摊"); + } + + [ScriptMethod(name: "P1A 双手涂鸦指路", eventType: EventTypeEnum.Tether, + eventCondition: ["Id:regex:^(013F|0140)$"], userControl: true)] + public void ColorClashTetherRecord(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P1A_DoubleStyle) return; + const uint heavenBomb = 0x47A1; + const uint paintBomb = 0x47A0; + const uint succubus = 0x47A5; + const uint morbol = 0x47A4; + + var tetherObj = sa.GetById(ev.SourceId); + if (tetherObj == null) return; + List tetherObjs = [paintBomb, heavenBomb, morbol, succubus]; + + lock (_pd) + { + var tetherDataId = tetherObj.DataId; + if (!tetherObjs.Contains(tetherDataId)) return; + var tetherObjDir = tetherObj.Position.Position2Dirs(Center, 4, true); + + switch (tetherDataId) + { + // 前四个index对应安全象限,从右上开始,顺时针增加,即1~4象限,用index0~3表示 + // 危险的象限会增加数值,取最小值对应的index即为安全区。 + case paintBomb: + // 固定 + sa.Log.Debug($"检测到爆弹,2、3象限危险"); + _pd.AddPriority(2, 10); + _pd.AddPriority(3, 10); + break; + case heavenBomb: + // 固定 + sa.Log.Debug($"检测到飞弹,0、1象限危险"); + _pd.AddPriority(0, 10); + _pd.AddPriority(1, 10); + break; + case succubus: + { + var safeDir2 = (tetherObjDir + 4 - 1) % 4; + sa.Log.Debug($"检测到梦魔,{tetherObjDir}、{safeDir2}象限危险"); + _pd.AddPriority(tetherObjDir, 10); + _pd.AddPriority(safeDir2, 10); + break; + } + case morbol: + { + var safeDir1 = (tetherObjDir + 4 + 1) % 4; + var safeDir2 = (tetherObjDir + 4 + 2) % 4; + sa.Log.Debug($"检测到魔界花,{safeDir1}、{safeDir2}象限危险"); + _pd.AddPriority(safeDir1, 10); + _pd.AddPriority(safeDir2, 10); + break; + } + default: + return; + } + + // 直到获得唯一一个安全区,停止记录并输出结果 + var posDictSecond = _pd.SelectSpecificPriorityIndex(1); + if (posDictSecond.Value == 0) return; + sa.Log.Debug($"{_pd.ShowPriorities(false)}"); + + List safePosList = Enumerable.Repeat(new Vector3(0, 0, 0), 20).ToList(); + + // 飞行位置 + safePosList[2] = new Vector3(112, 0, 88); + safePosList[0] = safePosList[2].RotatePoint(Center, 180f.DegToRad()); + safePosList[1] = safePosList[2].RotatePoint(Center, 270f.DegToRad()); + safePosList[3] = safePosList[2].RotatePoint(Center, 90f.DegToRad()); + + safePosList[4] = new Vector3(119, 0, 92); // 第0象限 左 + safePosList[5] = new Vector3(108, 0, 81); // 第0象限 右 + safePosList[6] = new Vector3(112, 0, 88); // 第0象限 前 + safePosList[7] = new Vector3(118, 0, 82); // 第0象限 后 + + safePosList[8] = safePosList[5].FoldPointVertical(Center.Z); // 第1象限 左 + safePosList[9] = safePosList[4].FoldPointVertical(Center.Z); // 第1象限 右 + safePosList[10] = safePosList[6].FoldPointVertical(Center.Z); // 第1象限 前 + safePosList[11] = safePosList[7].FoldPointVertical(Center.Z); // 第1象限 后 + + safePosList[12] = safePosList[4].PointCenterSymmetry(Center); // 第2象限 左 + safePosList[13] = safePosList[5].PointCenterSymmetry(Center); // 第2象限 右 + safePosList[14] = safePosList[6].PointCenterSymmetry(Center); // 第2象限 前 + safePosList[15] = safePosList[7].PointCenterSymmetry(Center); // 第2象限 后 + + safePosList[16] = safePosList[5].FoldPointHorizon(Center.X); // 第3象限 左 + safePosList[17] = safePosList[4].FoldPointHorizon(Center.X); // 第3象限 右 + safePosList[18] = safePosList[6].FoldPointHorizon(Center.X); // 第3象限 前 + safePosList[19] = safePosList[7].FoldPointHorizon(Center.X); // 第3象限 后 + + // 根据玩家身份建立安全对角位置 + var isPartnerStack = _pd.Priorities[4] == 2; + var basePosIdx = sa.GetMyIndex() switch + { + 0 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 0 : 2 : 0, // MT CN左,G8前,44分摊左 + 1 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 1 : 1 : 1, // ST CN右,G8右,44分摊右 + 2 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 2 : 0 : 0, // H1 CN前,G8左,44分摊左 + 3 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 3 : 3 : 1, // H2 CN后,G8后,44分摊右 + 4 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 0 : 2 : 0, // D1 CN左,G8前,44分摊左 + 5 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 1 : 1 : 1, // D2 CN右,G8右,44分摊右 + 6 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 2 : 0 : 0, // D3 CN前,G8左,44分摊左 + 7 => isPartnerStack ? GlobalStrat == StgEnum.CnServer ? 3 : 3 : 1, // D4 CN右,G8右,44分摊右 + _ => 0, + }; + var safePosDir = _pd.SelectSpecificPriorityIndex(0).Key; + var safePos1 = safePosList[safePosDir]; + var safePos2 = safePosList[4 * (safePosDir + 1) + basePosIdx]; + sa.Log.Debug($"先飞去第{safePosDir}象限(0~3),再去{basePosIdx}(0~3,左右前后)"); + + var dp1 = sa.DrawGuidance(safePos1, 0, 8000, $"飞行分摊1"); + var dp2 = sa.DrawGuidance(safePos2, 8000, 7000, $"飞行分摊2"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + } + } + + [ScriptMethod(name: "P1A 双手涂鸦指路删除", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(426(39|40))$"], userControl: Debugging)] + public void ColorClashRemove(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"飞行分摊1"); + sa.Method.RemoveDraw($"飞行分摊2"); + } + + #endregion P1 开场 + + #region P2 沙漠 + + [ScriptMethod(name: "---- P2 沙漠 ----", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: true)] + public void P2_SplitLine(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P2A - 沙漠 阶段转换", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42600"], userControl: Debugging)] + public void P2A_PhaseChange(Event ev, ScriptAccessory sa) + { + _M6sPhase = M6SPhase.P2A_Sand; + _pd.Init(sa, $"沙漠安全区", 4); + sa.Log.Debug($"当前阶段为:{_M6sPhase}"); + } + + [ScriptMethod(name: "P2A - 仙人掌范围(Imgui)", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(42657|39468)$"], userControl: true)] + public void P2A_SpraySpin1Range(Event ev, ScriptAccessory sa) + { + var isFirst = ev.ActionId == 42657; + var dp = sa.DrawCircle(ev.SourceId, 10, isFirst ? 3000: 0, isFirst ? 4000: 8500, $"仙人掌钢铁{ev.SourceId}"); + dp.Color = isFirst ? ColorHelper.ColorRed.V4.WithW(0.75f): sa.Data.DefaultDangerColor.WithW(0.75f); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "P2A - 仙人掌范围删除", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(42657|39468)$"], userControl: Debugging)] + public void P2A_SpraySpinRangeRemove(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"仙人掌钢铁{ev.SourceId}"); + } + + [ScriptMethod(name: "P2A - 116分散指路", eventType: EventTypeEnum.StatusAdd, + eventCondition: ["StatusID:4454"], userControl: true)] + public void P2A_HeatingUpSpread(Event ev, ScriptAccessory sa) + { + if (ev.TargetId != sa.Data.Me) return; + if (ev.DurationMilliseconds() > 50000) return; + var myIndex = sa.GetMyIndex(); + var flag = 1; + + if (myIndex >= 4) flag *= -1; + if (GlobalStrat == StgEnum.CnServer) flag *= -1; + + var bottomLeft = new Vector3(85, 0, 105); + var bottomRight = bottomLeft.FoldPointHorizon(Center.X); + + var dp = sa.DrawGuidance(flag > 0 ? bottomLeft : bottomRight, 39000, 4000, $"沙漠分散"); + sa.Log.Debug($"116分散flag(场基正左负右):{flag}"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "P2A - 116分散指路移除", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:42658", "TargetIndex:1"], userControl: Debugging)] + public void P2A_HeatingUpSpreadRemove(Event ev, ScriptAccessory sa) + { + if (ev.TargetId != sa.Data.Me) return; + sa.Method.RemoveDraw($"沙漠分散"); + } + + [ScriptMethod(name: "P2A - 流沙四角站位指路", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:39468"], userControl: true)] + public void P2A_QuickSandCorner(Event ev, ScriptAccessory sa) + { + if (_bools[0]) return; + List corners = Enumerable.Repeat(new Vector3(0, 0, 0), 4).ToList(); + corners[0] = new Vector3(119, 0, 81); + corners[1] = corners[0].RotatePoint(Center, 90f.DegToRad()); + corners[2] = corners[0].RotatePoint(Center, 180f.DegToRad()); + corners[3] = corners[0].RotatePoint(Center, 270f.DegToRad()); + + lock (_bools) + { + var spos = ev.SourcePosition; + var dangerDir = -1; + for (int i = 0; i < 4; i++) + { + var distance = spos.DistanceTo(corners[i]); + if (distance <= 10f) + { + dangerDir = i; + break; + } + } + + sa.Log.Debug($"检查{spos}坐标的仙人掌,得到dangerDir: {dangerDir}"); + if (dangerDir == -1) return; + _bools[0] = true; + + var myIndex = sa.GetMyIndex(); + var safeDir = myIndex switch + { + 2 => QuickSandStrat == QuickSandStgEnum.BattleFieldBasis_场基 + ? (dangerDir != 3 ? 3 : 2) : ((dangerDir + 3 + 4) % 4), + 3 => QuickSandStrat == QuickSandStgEnum.BattleFieldBasis_场基 + ? (dangerDir != 1 ? 1 : 2) : ((dangerDir + 1 + 4) % 4), + _ => QuickSandStrat == QuickSandStgEnum.BattleFieldBasis_场基 + ? (dangerDir != 0 ? 0 : 2) : ((dangerDir + 2 + 4) % 4) + }; + + var dp = sa.DrawGuidance(corners[safeDir], 0, 5000, $"沙漠四角安全区"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + } + + [ScriptMethod(name: "P2B - 飞翔炸弹 阶段转换", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42627"], userControl: Debugging)] + public void P2B_PhaseChange(Event ev, ScriptAccessory sa) + { + _M6sPhase = M6SPhase.P2B_FlyingBomb; + sa.Log.Debug($"当前阶段为:{_M6sPhase}"); + } + + [ScriptMethod(name: "P2B - 沙坑位置记录", eventType: EventTypeEnum.EnvControl, + eventCondition: ["Index:regex:^(3[2345])$", "Flag:2"], userControl: Debugging)] + public void P2B_SandRegionRecord(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P2B_FlyingBomb) return; + var dbgStr = ev.Index() switch + { + 32 => "上侧沙坑", + 33 => "下侧沙坑", + 34 => "右侧沙坑", + 35 => "左侧沙坑", + _ => "未知", + }; + _numbers[0] = (int)ev.Index(); + sa.Log.Debug($"检测到{ev.Index()} = {ev.Flag()} => {dbgStr}, 赋值 {_numbers[0]}"); + } + + [ScriptMethod(name: "P2B - 双手涂鸦(炸弹)类型提示与指路", eventType: EventTypeEnum.Tether, + eventCondition: ["Id:regex:^(013F|0140)$"], userControl: true)] + public void P2B_FlyingBombTypeHint(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P2B_FlyingBomb) return; + if (ev.SourceId != sa.Data.Me) return; + var isFlyingBomb = ev.Id0() == 0x13F; + var hintStr = isFlyingBomb ? $"飞行炸弹,站外" : $"普通炸弹,站坑"; + sa.Log.Debug($"玩家为{(isFlyingBomb ? "飞行炸弹" : "普通炸弹")} {ev.Id0()}"); + sa.Method.TextInfo(hintStr, 6000); + + List points = + [ + // A沙坑基准 + // MT/D1普通,ST/D2普通,H1/D3普通,H2/D4普通 + // MT/D1飞行,ST/D2飞行,H1/D3飞行,H2/D4飞行 + new(95, 0, 95), new(105, 0, 95), new(85, 0, 90), new(115, 0, 90), + new(95, 0, 105), new(105, 0, 105), new(85, 0, 110), new(115, 0, 110) + ]; + var myPointIdx = sa.GetMyIndex() % 4 + (isFlyingBomb ? 4 : 0); + float rotateDeg = _numbers[0] switch + { + 32 => 0, + 33 => 180, + 34 => 90, + 35 => 270, + _ => 0, + }; + sa.Log.Debug($"根据沙坑位置,基于A点沙坑安全点旋转{rotateDeg} Deg"); + var myPoint = points[myPointIdx].RotatePoint(Center, rotateDeg.DegToRad()); + var dp = sa.DrawGuidance(myPoint, 0, 6000, $"炸弹就位点"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + #endregion P2 沙漠 + + #region P3 小怪 + + [ScriptMethod(name: "---- P3 小怪 ----", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: true)] + public void P3_SplitLine(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A - 小怪 阶段转换", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42661"], userControl: Debugging)] + public void P3A_PhaseChange(Event ev, ScriptAccessory sa) + { + _M6sPhase = M6SPhase.P3A_Mobs; + sa.Log.Debug($"当前阶段为:{_M6sPhase}"); + } + + [ScriptMethod(name: "P3A - 小怪 本体AOE提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42666"], userControl: true)] + public void P3A_AoeHint(Event ev, ScriptAccessory sa) + { + sa.Method.TextInfo($"本体正在释放AOE!", 4000, true); + } + + // [ScriptMethod(name: "P3A - 小怪 W1提示", eventType: EventTypeEnum.StartCasting, + // eventCondition: ["ActionId:42662"], userControl: true)] + // public void P3A_MobsWave1Hint(Event ev, ScriptAccessory sa) + // { + // var hintStr = ""; + // var myIndex = sa.GetMyIndex(); + // hintStr = myIndex switch + // { + // 0 => $"右B准备,拉Boss与松鼠,拉到下C", + // 1 => $"左D准备,拉羊", + // 6 => $"左D准备,放一段LB,引导猫", + // 7 => $"左D准备,放二段LB,引导猫", + // _ => $"左D准备,打羊,引导猫" + // }; + // var isWarning = false; + // if (myIndex is 0 or 1) + // { + // var myObj = sa.Data.MyObject; + // if (myObj == null) return; + // if (!myObj.HasStatusAny([1833u, 743u, 79u, 91u])) + // { + // isWarning = true; + // hintStr = $"【开启盾姿】," + hintStr; + // } + // } + // sa.Method.TextInfo(hintStr, 6000, isWarning); + // + // List dpList = + // [ + // sa.DrawGuidance(Center, myIndex == 0 ? new Vector3(110, 0, 100) : new Vector3(90, 0, 100), 0, 10000, + // $"W1指路") + // ]; + // if (myIndex == 0) + // dpList.Add(sa.DrawGuidance(new Vector3(110, 0, 100), new Vector3(100, 0, 110), 0, 10000, + // $"W1指路")); + // for (var i = 0; i < dpList.Count; i++) + // sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dpList[i]); + // } + // + // [ScriptMethod(name: "P3A - 小怪 W2提示", eventType: EventTypeEnum.StartCasting, + // eventCondition: ["ActionId:42663"], userControl: true)] + // public void P3A_MobsWave2Hint(Event ev, ScriptAccessory sa) + // { + // sa.Method.RemoveDraw($"W1指路"); + // + // var hintStr = ""; + // var myIndex = sa.GetMyIndex(); + // hintStr = myIndex switch + // { + // 0 => $"下C准备,拉松鼠群,至右上鱼、左上鱼", + // 1 => $"跟随MT,集火松鼠群与鱼", + // 3 => $"右上就位,引导鱼", + // 6 => $"左上就位,引导鱼", + // _ => $"跟随MT,集火松鼠群与鱼" + // }; + // var isWarning = false; + // if (myIndex is 1) + // { + // var myObj = sa.Data.MyObject; + // if (myObj == null) return; + // if (myObj.HasStatusAny([1833u, 743u, 79u, 91u])) + // { + // isWarning = true; + // hintStr = $"【关启盾姿】," + hintStr; + // } + // } + // sa.Method.TextInfo(hintStr, 6000, isWarning); + // } + + #endregion P3 小怪 + + #region P4 山川 + + [ScriptMethod(name: "---- P4 山川 ----", eventType: EventTypeEnum.NpcYell, + eventCondition: ["HelloayaWorld:asdf"], userControl: true)] + public void P4_SplitLine(Event ev, ScriptAccessory sa) + { + } + + #region P4A 雷火箭头 + + [ScriptMethod(name: "P4A - 山川 阶段转换", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42595"], userControl: Debugging)] + public void P4A_PhaseChange(Event ev, ScriptAccessory sa) + { + _M6sPhase = M6SPhase.P4A_Bridge; + InitParams(); + sa.Log.Debug($"当前阶段为:{_M6sPhase}"); + } + + [ScriptMethod(name: "P4A - 箭头位置记录", eventType: EventTypeEnum.Tether, + eventCondition: ["Id:0140"], userControl: Debugging)] + public void P4A_ArrowRecord(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P4A_Bridge) return; + var spos = ev.SourcePosition; + lock (_numbers) + { + _numbers[1]++; + if (spos.Z < 80 & spos.X < 85) + _bools[0] = true; + if (_numbers[1] != 6) return; + sa.Log.Debug($"箭头检测完毕,需在每四格{(_bools[0] ? "右下" : "左上")}就位"); + _events[0].Set(); + } + } + + [ScriptMethod(name: "P4A - 雷火箭指路", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(4263[13])$"], userControl: true)] + public void P4A_StackOrSpread(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P4A_Bridge) return; + var aid = ev.ActionId; + var isStack = aid == 42631; + sa.Log.Debug($"Boss读条:{(isStack ? "火分摊": "雷分散")}"); + _events[0].WaitOne(); + + List points = GlobalStrat switch + { + StgEnum.CnServer => + [ + // 左上就位:MT/H1/D1/D2分摊,ST/H2/D3/D4分摊 + new(85f, 0, 108f), new(98.5f, 0, 98.5f), + // 右下就位:MT/H1/D1/D2分摊,ST/H2/D3/D4分摊 + new(87.5f, 0, 105.5f), new(101.5f, 0, 101.5f), + // 左上就位:MT/ST/H1/H2/D1/D2/D3/D4分散, + new(94.45f, 0, 97.62f), new(110.5f, 0, 96.5f), new(84.8f, 0, 112.7f), new(108.3f, 0, 112.6f), + new(83f, 0, 96.5f), new(96.5f, 0, 110.5f), new(83f, 0, 83f), new(110.5f, 0, 83f), + // 右下就位:MT/ST/H1/H2/D1/D2/D3/D4分散, + new(105.5f, 0, 105.5f), new(117f, 0, 103.5f), new(89.5f, 0, 117f), new(116.8f, 0, 116.8f), + new(90.5f, 0, 103.65f), new(103f, 0, 117f), new(89.5f, 0, 89.5f), new(117f, 0, 90f), + ], + _ => + [ + // 左上就位:MT/H1/D1/D2分摊,ST/H2/D3/D4分摊 + new(98.5f, 0, 98.5f), new(85f, 0, 108f), + // 右下就位:MT/H1/D1/D2分摊,ST/H2/D3/D4分摊 + new(101.5f, 0, 101.5f), new(87.5f, 0, 105.5f), + // 左上就位:MT/ST/H1/H2/D1/D2/D3/D4分散, + new(96.5f, 0, 83), new(96.5f, 0, 110.5f), new(83f, 0, 96.5f), new(110.5f, 0, 96.5f), + new(84.8f, 0, 112.7f), new(108.3f, 0, 112.6f), new(83f, 0, 83f), new(110.5f, 0, 83f), + // 右下就位:MT/ST/H1/H2/D1/D2/D3/D4分散, + new(103f, 0, 91.5f), new(103f, 0, 117f), new(90.5f, 0, 103.65f), new(117f, 0, 103.5f), + new(89.5f, 0, 117f), new(116.8f, 0, 116.8f), new(89.5f, 0, 89.5f), new(117f, 0, 90f), + ], + }; + + var myIndex = sa.GetMyIndex(); + var isMtGroup = myIndex is 0 or 2 or 4 or 5; + // 0 2 4 5, 1 3 6 7 + var myPointIdx = isStack ? (isMtGroup ? 0 : 1) : myIndex + 4; + myPointIdx += (_bools[0] ? (isStack ? 2 : 8) : 0); + + var dp = sa.DrawGuidance(points[myPointIdx], 0, 6000, $"雷火就位"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + _events[0].Reset(); + } + + #endregion P4A 雷火箭头 + + #region P4B 雷云 + + [ScriptMethod(name: "P4B - 雷云 阶段转换", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42648"], userControl: Debugging)] + public void P4B_PhaseChange(Event ev, ScriptAccessory sa) + { + _M6sPhase = M6SPhase.P4B_Light; + InitParams(); + sa.Log.Debug($"当前阶段为:{_M6sPhase}"); + } + + private struct CloudInfo(Vector3 cloudPos, float cloudRotation, int cloudDir, int cloudRotDir) + { + public Vector3 CloudPos = cloudPos; + public float CloudRotation = cloudRotation; + public int CloudDir = cloudDir; + public int CloudRotDir = cloudRotDir; + + public static CloudInfo Default() + { + return new CloudInfo(Vector3.Zero, 0, -1, -1); + } + } + + private CloudInfo GetCloudInfo(ScriptAccessory sa) + { + var cloudCharaEnum = sa.GetByDataId(18339).ToList(); + if (!cloudCharaEnum.Any()) + { + sa.Log.Error($"cloudCharaEnum 未找到雷云实体"); + return CloudInfo.Default(); + } + var cloudChara = cloudCharaEnum.First(); + if (cloudChara == null) + { + sa.Log.Error($"cloudChara 未找到雷云实体"); + return CloudInfo.Default(); + } + var cloudPos = cloudChara.Position; + var cloudDir = cloudPos.Position2Dirs(Center, 3, false); + var cloudRotation = cloudChara.Rotation; + + var dp = sa.DrawGuidance((ulong)cloudChara.EntityId, (ulong)0, 0, 4000, $"面向"); + dp.Scale = new Vector2(1, 4); + dp.ScaleMode = ScaleMode.None; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + var cloudRotDir = cloudRotation.Game2Logic().Rad2Dirs(6, false); + sa.Log.Debug($"检测到雷云位置:{cloudPos},面向角度:{cloudRotation.RadToDeg()}, 逻辑基:{cloudRotation.Game2Logic().RadToDeg()}, 位于方位{cloudDir}, 面向分割{cloudRotDir}"); + + // 检测是否为刚出生,无旋转角度 + if (cloudRotDir is 3) + cloudRotDir = Math.Abs(cloudRotation.Game2Logic().RadToDeg() - 180) < 1 ? -1 : cloudRotDir; + + return new CloudInfo(cloudPos, cloudRotation.RadToDeg(), cloudDir, cloudRotDir); + } + + [ScriptMethod(name: "P4B - 雷云 初始就位位置", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:18339"], userControl: true)] + public void P4B_FirstSafePoint(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P4B_Light) return; + var cloudStruct = GetCloudInfo(sa); + List crowdSafePos = [BridgePos[0], BridgePos[1], BridgePos[2]]; + var dp = sa.DrawGuidance(crowdSafePos[cloudStruct.CloudDir], 0, 3500, $"初始安全位置"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "P4B - 雷云 移动中就位位置", eventType: EventTypeEnum.TargetIcon, + eventCondition: ["Id:025A"], userControl: true)] + public async void P4B_RemainingSafePoint(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P4B_Light) return; + lock (_numbers) + { + _numbers[0]++; + if (ev.TargetId == sa.Data.Me) + _bools[0] = true; + if (_numbers[0] % 2 == 1) return; + } + await Task.Delay(1000); + var cloudStruct = GetCloudInfo(sa); + if (cloudStruct.CloudDir == -1) + { + sa.Log.Error($"步骤出错,返回"); + return; + } + // 雷云在右上、下、左上,人群位置 + List crowdSafePos = [BridgePos[0], BridgePos[1], BridgePos[2]]; + + // 雷云右上,左、右;雷云在下,左、右;雷云在左上,左、右 + List targetSafePos = [IslandPos[2], IslandPos[1], IslandPos[0], IslandPos[2], IslandPos[1], IslandPos[0]]; + + var crowdSafeIdx = cloudStruct.CloudRotDir switch + { + 0 => 0, + 1 => 0, + 2 => 1, + 3 => 1, + 4 => 2, + 5 => 2, + _ => 0, + }; + + var myPos = _bools[0] + ? targetSafePos[crowdSafeIdx * 2 + (sa.GetMyIndex() > 3 ? 1 : 0)] + : crowdSafePos[crowdSafeIdx]; + var dp = sa.DrawGuidance(myPos, 0, 6000, $"人群安全位置"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + _bools[0] = false; + } + + #endregion P4B 雷云 + + #region P4C 岩浆 + + [ScriptMethod(name: "P4C - 岩浆 阶段转换", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42649"], userControl: Debugging)] + public void P4C_PhaseChange(Event ev, ScriptAccessory sa) + { + _M6sPhase = M6SPhase.P4C_Lava; + InitParams(); + _towers.RefreshParam(sa, false); + sa.Log.Debug($"当前阶段为:{_M6sPhase}"); + } + + [ScriptMethod(name: "P4C - 岩浆 就位提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42649"], userControl: true)] + public void P4C_StandPoint(Event ev, ScriptAccessory sa) + { + // MT, ST, H1, H2, D1, D2, D3, D4 + // 国服H1D3在右上岛,H2D4在左上岛 + List standPoint = GlobalStrat switch + { + StgEnum.CnServer => [ + IslandPos[1], IslandPos[1], IslandPos[0], IslandPos[2], + IslandPos[1], IslandPos[1], IslandPos[0], IslandPos[2] + ], + _ => [ + IslandPos[1], IslandPos[1], IslandPos[2], IslandPos[0], + IslandPos[1], IslandPos[1], IslandPos[2], IslandPos[0] + ], + }; + + var dp = sa.DrawGuidance(standPoint[sa.GetMyIndex()], 0, 6000, $"岩浆就位"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + public class Towers + { + // ReSharper disable once NullableWarningSuppressionIsUsed + public ScriptAccessory sa {get; set;} = null!; + public int TowerNum { get; set; } = 0; + public bool IsSecondRound { get; set; } = false; + public List TowerList = []; + + public void RefreshParam(ScriptAccessory accessory, bool isSecondRound) + { + sa = accessory; + TowerNum = 0; + TowerList = []; + IsSecondRound = isSecondRound; + } + + public string TowerInfo(Tower tower) + { + var str = ""; + str += $"塔 {tower.TowerIdx},位于岛 {tower.IslandIdx}, 与中心成角 {tower.Rotation.RadToDeg()}, " + + $"属于玩家 {sa.GetPlayerJobByIndex(tower.PlayerIdx)}, 坐标 {tower.TowerPos}"; + return str; + } + + public void AddTower(Tower tower) + { + TowerList.Add(tower); + TowerNum++; + sa.Log.Debug($"添加:{TowerInfo(tower)}"); + } + + public void SortTowerListByRotation() + { + // 升序排序 + TowerList.Sort((towerA, towerB) => towerA.Rotation.CompareTo(towerB.Rotation)); + } + + public void SetTowerPlayer(List towerIdxList, int playerIdx) + { + sa.Log.Debug($"接受指令:为塔 {sa.BuildListStr(towerIdxList)} 赋值 {sa.GetPlayerJobByIndex(playerIdx)}"); + foreach (var towerIdx in towerIdxList) + { + var idx = TowerList.FindIndex(tower => tower.TowerIdx == towerIdx); + if (idx == -1) + { + sa.Log.Error($"未在TowerList中找到 {towerIdx}"); + continue; + } + if (playerIdx is < 0 or > 8) + { + sa.Log.Error($"输入的playerIdx {playerIdx} 不合法"); + continue; + } + var tower = TowerList[idx]; + tower.PlayerIdx = playerIdx; + TowerList[idx] = tower; + sa.Log.Debug($"将塔 {towerIdx} 分配给了 {sa.GetPlayerJobByIndex(playerIdx)}, {tower.TowerPos}"); + } + } + + public void SetTowerPlayerByListIdx(int idx, int playerIdx) + { + var tower = TowerList[idx]; + sa.Log.Debug($"将塔 {tower.TowerIdx} 分配给了 {sa.GetPlayerJobByIndex(playerIdx)}"); + tower.PlayerIdx = playerIdx; + TowerList[idx] = tower; + } + + public Tower GetTowerByPlayerIdx(int playerIdx) + { + return TowerList.Find(tower => tower.PlayerIdx == playerIdx); + } + + public MostTowerInfo FindMostTowerIslandIdx() + { + List towerNum = [0, 0, 0]; + foreach (var tower in TowerList) + { + towerNum[tower.IslandIdx]++; + } + MostTowerInfo result = new MostTowerInfo + { + MaxIslandIdx = towerNum.IndexOf(towerNum.Max()), + MaxTowerNum = towerNum.Max() + }; + return result; + } + + public struct MostTowerInfo + { + public int MaxIslandIdx { get; set; } + public int MaxTowerNum { get; set; } + } + } + + public record struct Tower + { + public Vector3 TowerPos; + public int TowerIdx; + public int IslandIdx; + public float Rotation; + public int PlayerIdx = -1; + + public Tower(int towerIdx) + { + TowerIdx = towerIdx; + if (towerIdx is < 69 or > 96) + throw new ArgumentException("非法的Tower Index"); + TowerPos = TowerPosList[towerIdx - 69]; + IslandIdx = towerIdx switch + { + < 79 => 2, + (>= 79 and < 89) => 0, + >= 89 => 1 + }; + Rotation = TowerPos.FindRadian(CenterRiver); + } + } + + [ScriptMethod(name: "P4C, P4D - 塔记录", eventType: EventTypeEnum.EnvControl, + eventCondition: ["Flag:2"], userControl: Debugging)] + public void P4C_TowerRecord(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P4C_Lava & _M6sPhase != M6SPhase.P4D_FlyLava) return; + var envIdx = ev.Index(); + if (envIdx is < 69 or > 96) return; + lock (_towers) + { + _towers.AddTower(new Tower((int)envIdx)); + if (_towers.TowerNum != 8) return; + sa.Log.Debug($"完成 第 {(_towers.IsSecondRound ? "二" : "一")} 轮,8 座塔的记录。"); + _events[0].Set(); + } + } + + [ScriptMethod(name: "P4C - 等待旋风、分散、踩塔提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:42682"], userControl: true)] + public void P4C_TwisterHint(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P4C_Lava) return; + const string str = "机制后,等待旋风,再踩塔"; + sa.Method.TextInfo(str, 4000, true); + var myIndex = sa.GetMyIndex(); + if (myIndex is 0 or 1 or 4 or 5) + { + List baitPos = [new(97, 0, 111.5f), new(103, 0, 111.5f), new(98, 0, 114), new(102, 0, 114)]; + var myBaitIdx = myIndex > 2 ? myIndex - 2 : myIndex; + var dp = sa.DrawGuidance(baitPos[myBaitIdx], 0, 5000, $"近战分散引导"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + _events[0].WaitOne(); + + // 分配塔 + // 此处为Game8,国服因初始岛就位不同需修改 + _towers.SetTowerPlayer([96], 5); // 固定值,右下塔,D2 + _towers.SetTowerPlayer([95], 1); // 固定值,右上塔,ST + _towers.SetTowerPlayer([94], 4); // 固定值,左下塔,D1 + _towers.SetTowerPlayer([93], 0); // 固定值,左上塔,MT + _towers.SetTowerPlayer([69, 72, 74, 76, 78], GlobalStrat == StgEnum.CnServer ? 7 : 2); // 左上塔,场基左,Game8 H1, 国服D4 + _towers.SetTowerPlayer([70, 71, 73, 75, 77], GlobalStrat == StgEnum.CnServer ? 3 : 6); // 左上塔,场基右,Game8 D3, 国服H2 + _towers.SetTowerPlayer([79, 81, 83, 85, 87], GlobalStrat == StgEnum.CnServer ? 6 : 3); // 右上塔,场基左,Game8 H2, 国服D3 + _towers.SetTowerPlayer([80, 82, 84, 86, 88], GlobalStrat == StgEnum.CnServer ? 2 : 7); // 右上塔,场基右,Game8 D4, 国服H1 + + var myTower = _towers.GetTowerByPlayerIdx(sa.GetMyIndex()); + var dp0 = sa.DrawStaticCircle(myTower.TowerPos, sa.Data.DefaultDangerColor.WithW(1.5f), 0, 8000, $"待踩塔范围", 3f); + var dp1 = sa.DrawStaticCircle(myTower.TowerPos, sa.Data.DefaultSafeColor.WithW(1.5f), 8000, 5000, $"踩塔范围", 3f); + var dp2 = sa.DrawGuidance(myTower.TowerPos, 8000, 5000, $"踩塔指路"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp0); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp1); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + sa.Log.Debug($"向塔 {myTower.TowerIdx} {myTower.TowerPos} 绘图"); + + _events[0].Reset(); + } + + #endregion P4C 岩浆 + + + + [ScriptMethod(name: "P4D - 飞行方向、目标塔", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:42614"], userControl: true)] + public void P4D_FlyingDestination(Event ev, ScriptAccessory sa) + { + sa.Log.Debug($"判断P4D - 飞行方向、目标塔"); + if (_M6sPhase != M6SPhase.P4D_FlyLava) return; + sa.Log.Debug($"进入P4D - 飞行方向、目标塔"); + _events[0].WaitOne(); + sa.Log.Debug($"解锁进入P4D - 飞行方向、目标塔"); + _towers.SortTowerListByRotation(); + + // Game8策略 + // 初始优先级列表,个位数为身份,十位数为顺时针顺序,H1-D3-H2-D4-D2-ST-MT-D1 + // 岛屿序号后续会根据百位数决定 + + // 国服策略 + // H左R右,无法分清则H中R远;T近M远(???) + // 顺时针顺序:D3-H1, D4-H2, D2-ST-MT-D1 + List priorityList = GlobalStrat switch + { + StgEnum.CnServer => [60, 51, 12, 33, 74, 45, 6, 27], + _ => [60, 51, 2, 23, 74, 45, 16, 37], + }; + + // 判断是否为南八塔 + var mostTowerInfo = _towers.FindMostTowerIslandIdx(); + if (mostTowerInfo is { MaxTowerNum: 8, MaxIslandIdx: 1 }) + { + _towers.SetTowerPlayer([89], GlobalStrat == StgEnum.CnServer ? 0 : 6); // 正上,国服MT,G8 D3 + _towers.SetTowerPlayer([90], GlobalStrat == StgEnum.CnServer ? 6 : 2); // 外左,国服D3,G8 H1 + _towers.SetTowerPlayer([91], GlobalStrat == StgEnum.CnServer ? 1 : 0); // 正下,国服ST,G8 MT + _towers.SetTowerPlayer([92], GlobalStrat == StgEnum.CnServer ? 7 : 5); // 外右,国服D4,G8 D2 + _towers.SetTowerPlayer([93], GlobalStrat == StgEnum.CnServer ? 2 : 3); // 左上,国服H1,G8 H2 + _towers.SetTowerPlayer([94], GlobalStrat == StgEnum.CnServer ? 4 : 4); // 左下,国服D1,G8 D1 + _towers.SetTowerPlayer([95], GlobalStrat == StgEnum.CnServer ? 3 : 7); // 右上,国服H2,G8 D4 + _towers.SetTowerPlayer([96], GlobalStrat == StgEnum.CnServer ? 5 : 1); // 右下,国服D2,G8 ST + } + else + { + // 根据四人塔位置,获得旋转方向 + var maxIslandIdx = mostTowerInfo.MaxIslandIdx; + if (maxIslandIdx == 0) + { + // 右上4人塔,集体逆时针 + priorityList[GlobalStrat == StgEnum.CnServer ? 3 : 2] += 100; // 国服H2 Game8 H1去下 + priorityList[GlobalStrat == StgEnum.CnServer ? 7 : 6] += 100; // 国服D4 Game8 D3去下 + priorityList[GlobalStrat == StgEnum.CnServer ? 2 : 3] += 200; // 国服H1 Game8 H2去左上 + priorityList[GlobalStrat == StgEnum.CnServer ? 6 : 7] += 200; // 国服D3 Game8 D4去左上 + } + else + { + // 左上4人塔,集体顺时针 + priorityList[0] += 200; // MT去左上 + priorityList[1] += 200; // ST去左上 + priorityList[4] += 200; // D1去左上 + priorityList[5] += 200; // D2去左上 + priorityList[GlobalStrat == StgEnum.CnServer ? 2 : 3] += 100; // 国服H1 Game8 H2去下 + priorityList[GlobalStrat == StgEnum.CnServer ? 6 : 7] += 100; // 国服D3 Game8 D4去下 + } + // 流程正义 + priorityList.Sort((a, b) => a.CompareTo(b)); + _towers.SetTowerPlayerByListIdx(0, priorityList[0] % 10); + _towers.SetTowerPlayerByListIdx(1, priorityList[1] % 10); + _towers.SetTowerPlayerByListIdx(2, priorityList[2] % 10); + _towers.SetTowerPlayerByListIdx(3, priorityList[3] % 10); + _towers.SetTowerPlayerByListIdx(4, priorityList[4] % 10); + _towers.SetTowerPlayerByListIdx(5, priorityList[5] % 10); + _towers.SetTowerPlayerByListIdx(6, priorityList[6] % 10); + _towers.SetTowerPlayerByListIdx(7, priorityList[7] % 10); + } + + // 时间点待测 + var myTower = _towers.GetTowerByPlayerIdx(sa.GetMyIndex()); + var dp0 = sa.DrawStaticCircle(myTower.TowerPos, sa.Data.DefaultDangerColor.WithW(1.5f), 0, 14000, $"待踩塔范围", 3f); + var dp1 = sa.DrawStaticCircle(myTower.TowerPos, sa.Data.DefaultSafeColor.WithW(1.5f), 14000, 5000, $"踩塔范围", 3f); + var dp2 = sa.DrawGuidance(myTower.TowerPos, 0, 14000, $"踩塔指路预备", isSafe: false); + var dp3 = sa.DrawGuidance(myTower.TowerPos, 14000, 5000, $"踩塔指路"); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp0); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Circle, dp1); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + sa.Log.Debug($"向塔 {myTower.TowerIdx} {myTower.TowerPos} 绘图"); + + _events[0].Reset(); + } + + [ScriptMethod(name: "P4D - 等待旋风提示", eventType: EventTypeEnum.StatusAdd, + eventCondition: ["StatusID:4163"], userControl: Debugging)] + public void P4D_TwisterHint(Event ev, ScriptAccessory sa) + { + if (_M6sPhase != M6SPhase.P4D_FlyLava) return; + if (ev.TargetId != sa.Data.Me) return; + const string str = "等待旋风,再踩塔"; + sa.Method.TextInfo(str, 6000, true); + } + #endregion P4 山川 + + 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; + } + + } + +} + +#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 ev) + { + return ParseHexId(ev["Id"], out var id) ? id : 0; + } + + public static uint Index(this Event ev) + { + return JsonConvert.DeserializeObject(ev["Index"]); + } + + public static uint Flag(this Event ev) + { + return JsonConvert.DeserializeObject(ev["Flag"]); + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + +} + +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); + } +} + +#region 计算函数 +public static class DirectionCalc +{ + 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,顺时针增加) + /// 算法与Logic2Game完全相同,但为了代码可读性,便于区分。 + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float Game2Logic(this float radian) + { + // if (r < 0) r = (float)(r + 2 * Math.PI); + // if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + + var r = float.Pi - radian; + r = (r + float.Pi * 2) % (float.Pi * 2); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// 算法与Game2Logic完全相同,但为了代码可读性,便于区分。 + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float Logic2Game(this float radian) + { + // var r = (float)Math.PI - radian; + // if (r < Math.PI) r = (float)(r + 2 * Math.PI); + // if (r > Math.PI) r = (float)(r - 2 * Math.PI); + + return radian.Game2Logic(); + } + + /// + /// 输入逻辑基角度,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 逻辑基角度 + /// 方位总数 + /// 斜分割,默认true + /// 逻辑基角度对应的逻辑方位 + public static int Rad2Dirs(this float radian, int dirs, bool diagDivision = true) + { + var r = diagDivision + ? Math.Round(radian / (2f * float.Pi / dirs)) + : Math.Floor(radian / (2f * float.Pi / dirs)); + r = (r + dirs) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 斜分割,默认true + /// 该坐标点对应的逻辑方位 + public static int Position2Dirs(this Vector3 point, Vector3 center, int dirs, bool diagDivision = true) + { + double dirsDouble = dirs; + var r = diagDivision + ? Math.Round(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble + : Math.Floor(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble; + return (int)r; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(this Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new Vector3(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(this Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new Vector3(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(this Vector3 newPoint, Vector3 center) + { + var radian = MathF.PI - MathF.Atan2(newPoint.X - center.X, newPoint.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointHorizon(this Vector3 point, float centerX) + { + return point with { X = 2 * centerX - point.X }; + } + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointVertical(this Vector3 point, float centerZ) + { + return point with { Z = 2 * centerZ - point.Z }; + } + + /// + /// 将输入点中心对称 + /// + /// 输入点 + /// 中心点 + /// + public static Vector3 PointCenterSymmetry(this Vector3 point, Vector3 center) + { + return point.RotatePoint(center, float.Pi); + } + + /// + /// 将输入点朝某中心点往内/外同角度延伸,默认向内 + /// + /// 待延伸点 + /// 中心点 + /// 延伸长度 + /// 是否向外延伸> + /// + public static Vector3 PointInOutside(this Vector3 point, Vector3 center, float length, bool isOutside = false) + { + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var targetPos = (point - center) / v2.Length() * length * (isOutside ? 1 : -1) + point; + return targetPos; + } + + /// + /// 获得两点之间距离 + /// + /// + /// + /// + public static float DistanceTo(this Vector3 point, Vector3 target) + { + Vector2 v2 = new(point.X - target.X, point.Z - target.Z); + return v2.Length(); + } + + /// + /// 获取给定数的指定位数 + /// + /// 给定数值 + /// 对应位数,个位为1 + /// + public static int GetDecimalDigit(this int val, int x) + { + string valStr = val.ToString(); + int length = valStr.Length; + + if (x < 1 || x > length) + { + return -1; + } + + char digitChar = valStr[length - x]; // 从右往左取第x位 + return int.Parse(digitChar.ToString()); + } + +} +#endregion 计算函数 + +#region 位置序列函数 +public static class IndexHelper +{ + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory accessory, ulong pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf((uint)pid); + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory accessory, 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]; + } +} +#endregion 位置序列函数 + +public static class ColorHelper +{ + public static ScriptColor ColorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor ColorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + public static ScriptColor ColorDark = new ScriptColor { V4 = new Vector4(0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorLightBlue = new ScriptColor { V4 = new Vector4(0.48f, 0.40f, 0.93f, 1.0f) }; + public static ScriptColor ColorWhite = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; + public static ScriptColor ColorYellow = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; +} + +#region 绘图函数 +public static class AssignDp +{ + /// + /// 返回箭头指引相关dp + /// + /// + /// 箭头起始,可输入uint或Vector3 + /// 箭头指向目标,可输入uint或Vector3,为0则无目标 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 箭头旋转角度 + /// 箭头宽度 + /// 使用安全色 + /// + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f, bool isSafe = true) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Rotation = rotation; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = isSafe ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + + if (ownerObj is uint or ulong) + { + dp.Owner = (ulong)ownerObj; + } + else if (ownerObj is Vector3 spos) + { + dp.Position = spos; + } + else + { + throw new ArgumentException("ownerObj的目标类型输入错误"); + } + + if (targetObj is uint or ulong) + { + if ((ulong)targetObj != 0) dp.TargetObject = (ulong)targetObj; + } + else if (targetObj is Vector3 tpos) + { + dp.TargetPosition = tpos; + } + else + { + throw new ArgumentException("targetObj的目标类型输入错误"); + } + + return dp; + } + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f, bool isSafe = true) + => accessory.DrawGuidance((ulong)accessory.Data.Me, targetObj, delay, destroy, name, rotation, scale, isSafe); + + /// + /// 返回距离某对象目标最近/最远的dp + /// + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图宽度 + /// 绘图长度 + /// true为最近,false为最远 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// + public static DrawPropertiesEdit DrawTargetNearFarOrder(this ScriptAccessory accessory, ulong ownerId, uint orderIdx, + bool isNear, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = + isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回距离某坐标位置最近/最远的dp + /// + /// + /// 特定坐标点 + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图宽度 + /// 绘图长度 + /// true为最近,false为最远 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// + public static DrawPropertiesEdit DrawPositionNearFarOrder(this ScriptAccessory accessory, Vector3 position, uint orderIdx, + bool isNear, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Position = position; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.TargetResolvePattern = + isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp.TargetOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回ownerId施法目标的dp + /// + /// + /// 起始目标id,通常为boss + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawOwnersTarget(this ScriptAccessory accessory, ulong ownerId, float width, float length, int delay, + int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回ownerId仇恨相关的dp + /// + /// + /// 起始目标id,通常为boss + /// 仇恨顺序,从1开始 + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawOwnersEnmityOrder(this ScriptAccessory accessory, ulong ownerId, uint orderIdx, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 圆圈尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory accessory, ulong ownerId, float scale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 绘图起始,可输入uint或Vector3 + /// 绘图目标,可输入uint或Vector3,为0则无目标 + /// 图形角度 + /// 旋转角度,以北为0度顺时针 + /// 绘图宽度 + /// 绘图长度 + /// 是Vector4则选用该颜色 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit DrawStatic(this ScriptAccessory accessory, object ownerObj, object targetObj, + float radian, float rotation, float width, float length, object color, int delay, int destroy, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + + if (ownerObj is uint or ulong) + { + dp.Owner = (ulong)ownerObj; + } + else if (ownerObj is Vector3 spos) + { + dp.Position = spos; + } + else + { + throw new ArgumentException("ownerObj的目标类型输入错误"); + } + + if (targetObj is uint or ulong) + { + if ((ulong)targetObj != 0) dp.TargetObject = (ulong)targetObj; + } + else if (targetObj is Vector3 tpos) + { + dp.TargetPosition = tpos; + } + else + { + throw new ArgumentException("ownerObj的目标类型输入错误"); + } + + dp.Radian = radian; + dp.Rotation = rotation.Logic2Game(); + + switch (color) + { + case Vector4 clr: + dp.Color = clr; + break; + default: + dp.Color = accessory.Data.DefaultDangerColor; + break; + } + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + /// + /// 返回静态圆圈dp,通常用于指引固定位置。 + /// + /// + /// 圆圈中心位置 + /// 圆圈颜色 + /// 圆圈尺寸,默认1.5f + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawStaticCircle(this ScriptAccessory accessory, Vector3 center, Vector4 color, + int delay, int destroy, string name, float scale = 1.5f) + => accessory.DrawStatic(center, (ulong)0, 0, 0, scale, scale, color, delay, destroy, name); + + /// + /// 返回矩形 + /// + /// 起始目标id,通常为自己或Boss + /// 矩形宽度 + /// 矩形长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory accessory, ulong ownerId, float width, float length, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回多方向延伸指引 + /// + /// + /// 分散源 + /// 分散角度 + /// 玩家对应角度idx + /// 指引箭头宽度 + /// 指引箭头长度 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 玩家对应箭头指引颜色 + /// 其他玩家对应箭头指引颜色 + /// + /// + public static List DrawExtendDirection(this ScriptAccessory accessory, object owner, + List extendDirs, int myDirIdx, float width, float length, int delay, int destroy, string name, + Vector4 colorPlayer, Vector4 colorNormal) + { + List dpList = []; + + + if (owner is uint or ulong) + { + for (var i = 0; i < extendDirs.Count; i++) + { + var dp = accessory.DrawRect((ulong)owner, width, length, delay, destroy, $"{name}{i}"); + dp.Rotation = extendDirs[i]; + dp.Color = i == myDirIdx ? colorPlayer : colorNormal; + dpList.Add(dp); + } + } + else if (owner is Vector3 spos) + { + for (var i = 0; i < extendDirs.Count; i++) + { + var dp = accessory.DrawGuidance(spos, spos.ExtendPoint(extendDirs[i], length), delay, destroy, + $"{name}{i}", 0, width); + dp.Color = i == myDirIdx ? colorPlayer : colorNormal; + dpList.Add(dp); + } + } + else + { + throw new ArgumentException("DrawExtendDirection的目标类型输入错误"); + } + + return dpList; + } + + /// + /// 将List内信息转换为字符串。 + /// + /// + /// + /// 是职业,在转为字符串前调用转职业函数 + /// + /// + public static string BuildListStr(this ScriptAccessory accessory, List myList, bool isJob = false) + { + return string.Join(", ", myList.Select(item => + { + if (isJob && item != null && item is int i) + return accessory.GetPlayerJobByIndex(i); + return item?.ToString() ?? ""; + })); + } +} + +#endregion 绘图函数 + +#endregion 函数集 + diff --git a/lianying1997/07-Dawntrail/Necron/NecronEx.cs b/lianying1997/07-Dawntrail/Necron/NecronEx.cs new file mode 100644 index 0000000..94e9ad0 --- /dev/null +++ b/lianying1997/07-Dawntrail/Necron/NecronEx.cs @@ -0,0 +1,1748 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using Lumina.Excel.Sheets; + +namespace UsamisKodakku.Scripts._07_DawnTrail.NecronEx; + +[ScriptType(name: Name, territorys: [1296], guid: "1829f7d6-9e64-4cf7-9be4-e5d8a2e03d21", + version: "0.0.0.7", author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)|F\] (Used|Cast))).*35501.*$ +// ^\[\w+\|[^|]+\|E\]\s\w+ + +public class NecronEx +{ + private const string + Name = "NecronEx [永恒之暗悲惶歼灭战]", + DebugVersion = "a"; + + const string NoteStr = + $""" + {Version} + 初版,鸭门。 + """; + + const string UpdateInfo = + $""" + {Version} + 1. 修复 MT/D1, ST/D2 组在青之二/四重波指路相反的错误。 + """; + + private const bool + Debugging = false; + + private static readonly + Vector3 Center = new Vector3(100, 0, 100); + + 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 static bool _initHint = false; + + private static List _poses = Enumerable.Repeat(new Vector3(0, 0, 0), 8).ToList(); + private static List _aetherBlightRec = []; + private static List _aetherBlightSourceRec = []; + private static List _markerPos = + [new Vector3(86.75f, 0, 94.28f), new Vector3(113.30f, 0, 94.35f), + new Vector3(90.31f, 0, 97.01f), new Vector3(109.58f, 0, 97.37f), + new Vector3(96.13f, 0, 85.74f), new Vector3(103.36f, 0, 85.92f), + new Vector3(97.66f, 0, 89.95f), new Vector3(101.94f, 0, 89.98f)]; + + private static int + // _castTime_FoD = 0, // Fear of Death 死之恐惧 + // _castTime_CG = 0, // Cold Grip 暗之死腕 + _castTime_MmM = 0; // Memento Mori + + private static bool + _judging_FoD = false, + _judging_CG = false, + _judging_MmM = false, + _judging_CoL = false; + + public void Init(ScriptAccessory sa) + { + RefreshParams(); + RefreshCastTimeParams(); + // sa.Log.Debug($"脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + _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(); + + _poses = Enumerable.Repeat(new Vector3(0, 0, 0), 8).ToList(); + _aetherBlightRec = []; + _aetherBlightSourceRec = []; + } + + private void RefreshCastTimeParams() + { + // 技能释放次数初始化 + // _castTime_FoD = 0; + // _castTime_CG = 0; + _castTime_MmM = 0; + + // 技能中初始化 + _judging_FoD = false; + _judging_CG = false; + _judging_MmM = false; + _judging_CoL = false; + } + + [ScriptMethod(name: "---- 测试项 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void SplitLine_Test(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "参数初始化", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 参数初始化(Event ev, ScriptAccessory sa) + { + RefreshParams(); + } + + [ScriptMethod(name: "消失吧", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 消失吧(Event ev, ScriptAccessory sa) + { + sa.WriteInvisible(sa.Data.MyObject); + } + + [ScriptMethod(name: "回来吧", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 回来吧(Event ev, ScriptAccessory sa) + { + sa.WriteVisible(sa.Data.MyObject); + } + + [ScriptMethod(name: "初始提示", eventType: EventTypeEnum.Chat, eventCondition: ["Message:regex:^(肉体无法摆脱死亡的束缚。\n这就是你们的局限性。)$"], + userControl: true)] + public void 初始提示(Event ev, ScriptAccessory sa) + { + if (_initHint) return; + _initHint = true; + var myIndex = sa.GetMyIndex(); + List role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + sa.Method.TextInfo( + $"你是【{role[myIndex]}】," + + $"若有误请及时调整。", 5000); + } + + [ScriptMethod(name: "---- 青之冲击 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 青之冲击分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "死刑青之冲击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44592)$"], + userControl: true)] + public void 青之冲击绘图(Event ev, ScriptAccessory sa) + { + var dp = sa.DrawFan(ev.SourceId, 5000, 5000, $"青之冲击", 100f.DegToRad(), 0, 100, 0, draw: false); + dp.SetOwnersEnmityOrder(1); + dp.Color = new Vector4(1, 0, 0, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + // sa.Log.Debug($"绘图 青之冲击"); + } + + [ScriptMethod(name: "青之冲击判定", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44593)$", "TargetIndex:1"], + userControl: Debugging)] + public void 青之冲击判定(Event ev, ScriptAccessory sa) + { + _numbers[0]++; + // sa.Log.Debug($"青之冲击计数 {_numbers[0]}"); + if (_numbers[0] % 2 != 0) return; + sa.Method.RemoveDraw($"青之冲击"); + // sa.Log.Debug($"删除青之冲击绘图,计数初始化"); + _numbers[0] = 0; + } + + #region 死之恐惧 Fear of Death + + [ScriptMethod(name: "---- 死之恐惧 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 死之恐惧分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "死之恐惧", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44550)$"], + userControl: Debugging)] + public void 死之恐惧(Event ev, ScriptAccessory sa) + { + // _castTime_FoD++; + // sa.Log.Debug($"读条死之恐惧 #{_castTime_FoD}"); + _judging_FoD = true; + + _numbers[1] = 0; + _bools[0] = true; + // sa.Log.Debug($"死之恐惧 #{_castTime_FoD} 添加事件 _events[0],直至记录完毕手与指路"); + _events[0].Set(); + + // switch (_castTime_FoD) + // { + // case 1: + // case 2: + // case 3: + // _numbers[1] = 0; + // _bools[0] = true; + // // sa.Log.Debug($"死之恐惧 #{_castTime_FoD} 添加事件 _events[0],直至记录完毕手与指路"); + // _events[0].Set(); + // break; + // default: + // break; + // } + } + + [ScriptMethod(name: "死之恐惧出手位置", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(18700)$"], + userControl: true)] + public void 死之恐惧出手位置(Event ev, ScriptAccessory sa) + { + if (!_judging_FoD) return; + _events[0].WaitOne(10000); + if (!_bools[0]) return; + + lock (_numbers) + { + // 记录位置 + _poses[_numbers[1]] = ev.SourcePosition; + _numbers[1]++; + // sa.Log.Debug($"记录下 手 #{_numbers[1]} 的泥坑位置 {ev.SourcePosition}"); + } + + // 记录全后,排序 + if (_numbers[1] != 8) return; + _poses.Sort((a, b) => { + // 先从左到右升序,后从上到下升序。 + int z = a.X.CompareTo(b.X); + return z != 0 ? z : a.Z.CompareTo(b.Z); + }); + + // MT H1 D1 D3 D2 D4 ST H2 + List playerIdx = [0, 2, 4, 6, 5, 7, 1, 3]; + + // 绘图与指路 + for (int i = 0; i < 8; i++) + { + sa.DrawCircle(_poses[i], 0, 20000, $"泥坑{i}", 3f); + if (sa.GetMyIndex() != playerIdx[i]) continue; + var downSide = i % 2 == 1; + sa.DrawGuidance(_poses[i] + new Vector3(0, 0, downSide ? 4 : -4), 0, 20000, $"泥坑指路{i}"); + // sa.Log.Debug($"指路向泥坑 #{i}(从左至右、从上至下),泥坑在{(downSide ? "下" : "上")}"); + } + + } + + [ScriptMethod(name: "死之恐惧泥坑绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44551)$"], + userControl: Debugging, suppress: 10000)] + public void 死之恐惧泥坑绘图删除(Event ev, ScriptAccessory sa) + { + if (!_judging_FoD) return; + _events[0].Reset(); + // sa.Log.Debug($"死之恐惧泥坑判定,绘图删除,释放锁"); + sa.Method.RemoveDraw($"泥坑.*"); + } + + [ScriptMethod(name: "死之恐惧压溃绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44551)$"], + userControl: true, suppress: 10000)] + public void 死之恐惧压溃绘图(Event ev, ScriptAccessory sa) + { + if (!_judging_FoD) return; + if (!_bools[0]) return; + // sa.Log.Debug($"进行引导压溃绘图"); + List playerIdx = [0, 2, 4, 6, 5, 7, 1, 3]; + for (int i = 0; i < 8; i++) + { + var isMyIdx = sa.GetMyIndex() == playerIdx[i]; + var dp = sa.DrawRect(_poses[i], 0, 20000, $"引导压溃{i}", + 0, 6, 24, isMyIdx, draw: false); + dp.SetPositionDistanceOrder(true, 1); + dp.Color = isMyIdx ? sa.Data.DefaultSafeColor : new Vector4(0, 0, 0, 2f); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + } + + [ScriptMethod(name: "死之恐惧压溃绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44552)$"], + userControl: Debugging, suppress: 10000)] + public void 死之恐惧压溃绘图删除(Event ev, ScriptAccessory sa) + { + if (!_judging_FoD) return; + // sa.Log.Debug($"死之恐惧压溃判定,绘图删除"); + sa.Method.RemoveDraw($"引导压溃.*"); + _judging_FoD = false; + } + + #endregion 死之恐惧 Fear of Death + + #region 暗之死腕 Cold Grip + + [ScriptMethod(name: "---- 暗之死腕 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 暗之死腕分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "暗之死腕", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4455[34])$"], + userControl: Debugging)] + public void 暗之死腕(Event ev, ScriptAccessory sa) + { + // _castTime_CG++; + // sa.Log.Debug($"读条暗之死腕 #{_castTime_CG}"); + _judging_CG = true; + + _bools[1] = true; + _bools[2] = ev.ActionId == 44553; // 左安全 + // sa.Log.Debug($"暗之死腕 #{_castTime_CG} 添加事件 _events[1],直至完成一段绘图"); + _events[1].Set(); + + // switch (_castTime_CG) + // { + // case 1: + // case 2: + // case 3: + // _bools[1] = true; + // _bools[2] = ev.ActionId == 44553; // 左安全 + // // sa.Log.Debug($"暗之死腕 #{_castTime_CG} 添加事件 _events[1],直至完成一段绘图"); + // _events[1].Set(); + // break; + // default: + // break; + // } + } + + [ScriptMethod(name: "暗之死腕一段绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4455[34])$"], + userControl: true)] + public void 暗之死腕一段绘图(Event ev, ScriptAccessory sa) + { + _events[1].WaitOne(10000); + if (!_bools[1]) return; + if (!_judging_CG) return; + + var isLeftSafe = _bools[2]; + // sa.Log.Debug($"暗之死腕 {(isLeftSafe ? "左" : "右")}安全,进行一段绘图"); + + sa.DrawRect(new Vector3(88, 0, 85), 0, 6000, $"暗之死腕一段", 0, 12, 100); + sa.DrawRect(new Vector3(112, 0, 85), 0, 6000, $"暗之死腕一段", 0, 12, 100); + + // 指引线 + for (int i = 0; i < 5; i++) + { + var dp = sa.DrawLine(new Vector3(isLeftSafe ? 95 : 105, 0, 90 + i * 5), + new Vector3(isLeftSafe ? 93 : 107, 0, 90 + i * 5), + 0, 20000, $"暗之死腕指引线", 0, 1f, 2f, true, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + } + + } + + [ScriptMethod(name: "暗之死腕二段绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44612)$"], + userControl: true, suppress: 1000)] + public void 暗之死腕二段绘图(Event ev, ScriptAccessory sa) + { + if (!_judging_CG) return; + if (!_bools[1]) return; + var isLeftSafe = _bools[2]; + // sa.Log.Debug($"暗之死腕 {(isLeftSafe ? "左" : "右")}安全,一段判定,进行二段绘图"); + sa.Method.RemoveDraw($"暗之死腕一段.*"); + sa.DrawRect(new Vector3(isLeftSafe ? 106 : 94, 0, 85), 0, 6000, $"暗之死腕二段", 0, 24, 100); + } + + [ScriptMethod(name: "暗之死腕绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44555)$"], + userControl: Debugging)] + public void 暗之死腕绘图删除(Event ev, ScriptAccessory sa) + { + if (!_judging_CG) return; + _events[1].Reset(); + // sa.Log.Debug($"暗之死腕二段判定,绘图删除,释放锁_events[1]"); + sa.Method.RemoveDraw($"暗之死腕.*"); + _judging_CG = false; + } + + #endregion 暗之死腕 Cold Grip + + #region 死亡警告 Memento Mori + + [ScriptMethod(name: "---- 死亡警告 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 死亡警告分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "死亡警告", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4456[56])$"], + userControl: Debugging)] + public void 死亡警告(Event ev, ScriptAccessory sa) + { + _castTime_MmM++; + // sa.Log.Debug($"读条死亡警告 #{_castTime_MmM}"); + _judging_MmM = true; + + _bools[2] = true; + _numbers[2] = 0; // 安全行 + _numbers[6] = 0; // 手计数 + // sa.Log.Debug($"死亡警告 #{_castTime_MmM} 添加事件 _events[2],直至确定安全行"); + _events[2].Set(); + } + + [ScriptMethod(name: "死亡警告中直线绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4456[56])$"], + userControl: true)] + public void 死亡警告中直线绘图(Event ev, ScriptAccessory sa) + { + _events[2].WaitOne(10000); + if (!_bools[2]) return; + if (!_judging_MmM) return; + sa.DrawRect(ev.SourceId, 0, 20000, $"死亡警告中线", 0, 12, 100); + _bools[3] = ev.ActionId == 44565; // 44565 左少右多 + var isLeftLess = _bools[3]; + // sa.Log.Debug($"死亡警告 {(isLeftLess ? "左" : "右")}少,进行绘图"); + } + + [ScriptMethod(name: "死亡警告手绘图", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^(18700)$"], + userControl: true)] + public void 死亡警告与手绘图(Event ev, ScriptAccessory sa) + { + if (!_judging_MmM) return; + _events[2].WaitOne(10000); + if (!_bools[2]) return; + + lock (_numbers) + { + _numbers[6]++; + sa.DrawRect(ev.SourceId, 0, 20000, $"死亡警告手", 0, 6, 24); + var isLeftLess = _bools[3]; + // 左少,则找面向左,rotation为270(-90);右少则找面向右,rotation为90 + var rotation = ev.SourceRotation.RadToDeg(); + // sa.Log.Debug($"找到 rotation 为 {rotation} 的手 #{_numbers[6]} {ev.SourcePosition} "); + + if ((isLeftLess && rotation > 180f) || (!isLeftLess && rotation < 180f)) + { + // Z轴为88 94 100 106 112,可通过(pos.z - 87) / 6 得到所在安全行并保存 + _numbers[2] = (int)Math.Floor((ev.SourcePosition.Z - 87) / 6); + // sa.Log.Debug($"死亡警告 {_castTime_MmM} 得到安全行 {_numbers[2]}"); + } + + if (_numbers[6] != 5) return; + // sa.Log.Debug($"死亡警告 {_castTime_MmM} 捕捉手完毕,释放锁 _events[2]"); + _numbers[6] = 0; + _events[2].Reset(); + + // sa.Log.Debug($"死亡警告 {_castTime_MmM} 添加事件 _events[3],直至完成绘图指路"); + _events[3].Set(); + } + } + + [ScriptMethod(name: "死亡警告指路(请保证上一项开启)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4456[56])$"], + userControl: true)] + public void 死亡警告指路(Event ev, ScriptAccessory sa) + { + _events[3].WaitOne(10000); + if (!_bools[2]) + { + _events[3].Reset(); + return; + } + if (!_judging_MmM) return; + + var safeRow = _numbers[2]; + var isLeftLess = _bools[3]; + var reverseBias = isLeftLess ? 0 : 1; + + // 若为第二次死亡警告,忽略 + if (_castTime_MmM == 2) + { + // sa.Log.Debug($"死亡警告 {_castTime_MmM} 忽略,释放锁 _events[3]"); + _events[3].Reset(); + return; + } + + // 死亡警告的安全行不可能在第一行与最后一行,因此可遵循以下指路规则 + // D1、D2,始终在第一行;D3、D4,始终在最后一行;MT、ST,始终在安全行偏上;H1、H2,始终在安全行偏下 + // 以左少基准进行计算,若相反则平移偏置 + var safePos = sa.GetMyIndex() switch + { + 4 => new Vector3(82.5f, 0, 85.5f) + new Vector3(24f * reverseBias, 0, 0), + 5 => new Vector3(93.5f, 0, 85.5f) + new Vector3(24f * reverseBias, 0, 0), + 6 => new Vector3(82.5f, 0, 114.5f) + new Vector3(24f * reverseBias, 0, 0), + 7 => new Vector3(93.5f, 0, 114.5f) + new Vector3(24f * reverseBias, 0, 0), + 0 => new Vector3(106.5f, 0, 85.5f + safeRow * 6) + new Vector3(-24f * reverseBias, 0, 0), + 1 => new Vector3(117.5f, 0, 85.5f + safeRow * 6) + new Vector3(-24f * reverseBias, 0, 0), + 2 => new Vector3(82.5f, 0, 85.5f + (safeRow + 1) * 6) + new Vector3(24f * reverseBias, 0, 0) + + new Vector3(0, 0, safeRow >= 3 ? -7 : 0), + 3 => new Vector3(93.5f, 0, 85.5f + (safeRow + 1) * 6) + new Vector3(24f * reverseBias, 0, 0) + + new Vector3(0, 0, safeRow >= 3 ? -7 : 0), + _ => new Vector3(100f, 0, 100f), + }; + sa.DrawGuidance(safePos, 0, 20000, $"死亡警告指路"); + // sa.Log.Debug($"死亡警告 {_castTime_MmM} 指路完毕,释放锁 _events[3]"); + + _events[3].Reset(); + } + + [ScriptMethod(name: "死亡警告绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44567)$"], + userControl: Debugging, suppress: 1000)] + public void 死亡警告绘图删除(Event ev, ScriptAccessory sa) + { + if (!_judging_MmM) return; + // sa.Log.Debug($"死亡警告 {_castTime_MmM} 手 压溃 判定,绘图删除"); + sa.Method.RemoveDraw($"死亡警告.*"); + _judging_MmM = false; + } + + #endregion 死亡警告 Memento Mori + + #region 青魂 Aether Blight + + [ScriptMethod(name: "---- 青魂 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 青魂分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "青魂头标记录", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(025[CDEF])$"], + userControl: Debugging)] + public void 青魂头标记录(Event ev, ScriptAccessory sa) + { + var id = ev.Id0(); + _aetherBlightRec.Add(id); + // sa.Log.Debug($"青魂 {id} 记录(#{_aetherBlightRec.Count}),604 钢铁、605 月环、606 打两侧、607 打中间"); + } + + [ScriptMethod(name: "青之波潮连线记录", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(015[BCDE])$"], + userControl: Debugging)] + public void 青之波潮连线记录(Event ev, ScriptAccessory sa) + { + _aetherBlightSourceRec.Add(ev.SourceId); + // sa.Log.Debug($"检测到青之波潮连线 #{_aetherBlightSourceRec.Count},连线 {ev.SourceId},位于 {ev.SourcePosition}"); + } + + [ScriptMethod(name: "青之多重波", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4455[78]|4516[78])$"], + userControl: Debugging)] + public void 青之多重波(Event ev, ScriptAccessory sa) + { + // 44557, 45167 二重,四四分摊 + // 44558, 45168 四重,二二分摊 + // 起始序列,默认为0,用于Season的转置 + + _numbers[4] = ev.ActionId is 44557 or 44558 ? 1 : 4; // 绘图总次数 + _bools[8] = ev.ActionId is 45167 or 45168; // 是青之波潮 + _bools[5] = ev.ActionId is 44558 or 45168; // 是二二分摊 + + _bools[4] = true; + // sa.Log.Debug($"青之多重{(_bools[8] ? "波潮" : "波")}准备绘图,共绘图 {_numbers[4]} 次"); + _events[4].Set(); + // sa.Log.Debug($"青之多重{(_bools[8] ? "波潮" : "波")} 添加事件 _events[4]"); + } + + [ScriptMethod(name: "青之多重波分摊绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4455[78])$"], + userControl: true)] + public void 青之多重波分摊绘图(Event ev, ScriptAccessory sa) + { + _events[4].WaitOne(10000); + if (!_bools[4]) return; + var isPartnerStack = _bools[5]; + List partnerJudge = [0, 11, 2, 13, 0, 11, 2, 13]; + + // 青之多重波,对目标分摊绘图(此处选择TH) + var myPartIdx = partnerJudge[sa.GetMyIndex()]; + sa.DrawFan(ev.SourceId, sa.Data.PartyList[2], 0, 20000, $"青之多重波分摊 H1", + (isPartnerStack ? 20f : 25f).DegToRad(), 0, 100, 0, isPartnerStack ? myPartIdx == partnerJudge[2] : myPartIdx < 10); + sa.DrawFan(ev.SourceId, sa.Data.PartyList[3], 0, 20000, $"青之多重波分摊 H2", + (isPartnerStack ? 20f : 25f).DegToRad(), 0, 100, 0, isPartnerStack ? myPartIdx == partnerJudge[3] : myPartIdx > 10); + + if (isPartnerStack) + { + sa.DrawFan(ev.SourceId, sa.Data.PartyList[0], 0, 20000, $"青之多重波分摊 MT", + (isPartnerStack ? 20f : 25f).DegToRad(), 0, 100, 0, myPartIdx == partnerJudge[0]); + sa.DrawFan(ev.SourceId, sa.Data.PartyList[1], 0, 20000, $"青之多重波分摊 ST", + (isPartnerStack ? 20f : 25f).DegToRad(), 0, 100, 0, myPartIdx == partnerJudge[1]); + } + // sa.Log.Debug($"青之 {(isPartnerStack ? "四" : "二")} 重波范围绘图完毕,释放锁"); + } + + [ScriptMethod(name: "青之多重波场地绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4455[78])$"], + userControl: true)] + public void 青之多重波场地绘图(Event ev, ScriptAccessory sa) + { + _events[4].WaitOne(10000); + if (!_bools[4]) return; + + var drawTotalCount = _numbers[4]; + if (drawTotalCount != _aetherBlightRec.Count) + { + sa.Log.Error($"绘图总次数 {drawTotalCount} 与记录List Count {_aetherBlightRec.Count} 不符,停止绘图。"); + return; + } + + // 604 钢铁、605 月环、606 打两侧、607 打中间 + switch (_aetherBlightRec[0]) + { + case 606: + sa.DrawRect(new Vector3(88, 0, 85), 0, 5500, $"青之多重波打两侧", 0, 12, 100); + sa.DrawRect(new Vector3(112, 0, 85), 0, 5500, $"青之多重波打两侧", 0, 12, 100); + // sa.Log.Debug($"青之多重波 打两侧 绘图完毕"); + break; + + case 607: + sa.DrawRect(new Vector3(100, 0, 78), 0, 5500, $"青之多重波打中间", 0, 12, 100); + // sa.Log.Debug($"青之多重波 打中间 绘图完毕"); + break; + + case 604: + sa.DrawCircle(new Vector3(100, 0, 78), 0, 5500, $"青之多重波钢铁", 20); + // sa.Log.Debug($"青之多重波 钢铁 绘图完毕"); + break; + + case 605: + sa.DrawDonut(new Vector3(100, 0, 78), 0, 5500, $"青之多重波月环", 60, 16); + // sa.Log.Debug($"青之多重波 月环 绘图完毕"); + break; + } + + // sa.Log.Debug($"青之多重波绘图完毕"); + } + + [ScriptMethod(name: "青之多重波指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4455[78])$"], + userControl: true)] + public void 青之多重波指路(Event ev, ScriptAccessory sa) + { + _events[4].WaitOne(10000); + if (!_bools[4]) return; + + var isPartnerStack = _bools[5]; + + // 青之多重波,指路 + var myIndex = sa.GetMyIndex(); + var isInsideSafe = _aetherBlightRec[0] is 605 or 606; + + var myPosIdx = myIndex % 4 + (isInsideSafe ? 4 : 0); + if (!isPartnerStack && myPosIdx % 4 <= 2) myPosIdx += 2; + sa.Log.Debug($"是分组分摊:{isPartnerStack},是内安全:{isInsideSafe},我的位置:{myPosIdx}"); + sa.DrawGuidance(_markerPos[myPosIdx], 0, 20000, $"青之多重波指路"); + + // sa.Log.Debug($"青之 {(isPartnerStack ? "四" : "二")} 指路 {_markerPos[myPosIdx]} 绘图完毕,释放锁"); + } + + [ScriptMethod(name: "青之多重波绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4455[78])$"], + userControl: Debugging)] + public void 青之多重波绘图删除(Event ev, ScriptAccessory sa) + { + _aetherBlightRec = []; + _aetherBlightSourceRec = []; + _events[4].Reset(); + // sa.Log.Debug($"青之多重波判定,清空矩阵,绘图删除,释放锁_events[4]"); + sa.Method.RemoveDraw($"青之多重波.*"); + } + + [ScriptMethod(name: "青之多重波潮判断", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4516[78])$"], + userControl: Debugging)] + public void 青之多重波潮判断(Event ev, ScriptAccessory sa) + { + _events[4].WaitOne(10000); + if (!_bools[4]) return; + int startIdx = 0; + float lowestY = 30f; + // 获得startIdx + for (int i = 0; i < _aetherBlightSourceRec.Count; i++) + { + // 找到Y最低的单位 + var obj = sa.GetById(_aetherBlightSourceRec[i]); + if (obj is null) continue; + if (obj.Position.Y >= lowestY) continue; + lowestY = obj.Position.Y; + startIdx = i; + } + _numbers[3] = startIdx; + _numbers[5] = 0; + // sa.Log.Debug($"经遍历,最低单位index为 {startIdx},id为 {_aetherBlightSourceRec[startIdx]},高度为 {lowestY}"); + _events[5].Set(); + } + + [ScriptMethod(name: "青之多重波潮场地绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4516[78])$"], + userControl: true)] + public void 青之多重波潮场地绘图(Event ev, ScriptAccessory sa) + { + _events[5].WaitOne(10000); + if (!_bools[4]) return; + _bools[6] = true; + + var drawTotalCount = _numbers[4]; + if (drawTotalCount != _aetherBlightRec.Count) + { + sa.Log.Error($"绘图总次数 {drawTotalCount} 与记录List Count {_aetherBlightRec.Count} 不符,停止绘图。"); + _bools[6] = false; + return; + } + + var startIdx = _numbers[3]; + // 604 钢铁、605 月环、606 打两侧、607 打中间 + switch (_aetherBlightRec[startIdx]) + { + case 606: + sa.DrawRect(new Vector3(88, 0, 85), 0, 15000, $"青之波潮0 打两侧", 0, 12, 100); + sa.DrawRect(new Vector3(112, 0, 85), 0, 15000, $"青之波潮0 打两侧", 0, 12, 100); + // sa.Log.Debug($"青之波潮0 打两侧 绘图完毕"); + break; + + case 607: + sa.DrawRect(new Vector3(100, 0, 78), 0, 15000, $"青之波潮0 打中间", 0, 12, 100); + // sa.Log.Debug($"青之波潮0 打中间 绘图完毕"); + break; + + case 604: + sa.DrawCircle(new Vector3(100, 0, 78), 0, 15000, $"青之波潮0 钢铁", 20); + // sa.Log.Debug($"青之波潮0 钢铁 绘图完毕"); + break; + + case 605: + sa.DrawDonut(new Vector3(100, 0, 78), 0, 15000, $"青之波潮0 月环", 60, 16); + // sa.Log.Debug($"青之波潮0 月环 绘图完毕"); + break; + } + } + + [ScriptMethod(name: "青之多重波潮计数器", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4518[345]|44608)$", "TargetIndex:1"], + userControl: Debugging, suppress: 1000)] + public void 青之多重波潮计数器(Event ev, ScriptAccessory sa) + { + _events[4].WaitOne(10000); + if (!_bools[8]) return; + _numbers[5]++; + // sa.Log.Debug($"青之波潮计数器增加 至 #{_numbers[5]},添加锁 _events[6] "); + _events[6].Set(); + } + + [ScriptMethod(name: "青之多重波潮计数器解锁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4518[345]|44608)$", "TargetIndex:1"], + userControl: Debugging, suppress: 1000)] + public async void 青之多重波潮计数器解锁(Event ev, ScriptAccessory sa) + { + _events[4].WaitOne(10000); + if (!_bools[8]) return; + _events[6].WaitOne(500); + + await Task.Delay(500); + // sa.Log.Debug($"青之多重波潮计数器 _events[6] 解锁"); + _events[6].Reset(); + } + + [ScriptMethod(name: "青之多重波潮场地绘图后续", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4518[345]|44608)$", "TargetIndex:1"], + userControl: Debugging, suppress: 1000)] + public void 青之多重波潮场地绘图后续(Event ev, ScriptAccessory sa) + { + if (!_bools[6]) return; + _events[6].WaitOne(10000); + var drawCount = _numbers[5]; + var drawTotalCount = _numbers[4]; + if (drawTotalCount != _aetherBlightRec.Count) + { + sa.Log.Error($"绘图总次数 {drawTotalCount} 与记录List Count {_aetherBlightRec.Count} 不符,停止绘图。"); + return; + } + // sa.Log.Debug($"删除青之波潮{drawCount-1} 绘图"); + sa.Method.RemoveDraw($"青之波潮{drawCount-1}.*"); + if (drawCount >= drawTotalCount) return; + + var startIdx = _numbers[3]; + var drawIdx = (startIdx + drawCount) % drawTotalCount; + + // 604 钢铁、605 月环、606 打两侧、607 打中间 + switch (_aetherBlightRec[drawIdx]) + { + case 606: + sa.DrawRect(new Vector3(88, 0, 85), 0, 15000, $"青之波潮{drawCount} 打两侧", 0, 12, 100); + sa.DrawRect(new Vector3(112, 0, 85), 0, 15000, $"青之波潮{drawCount} 打两侧", 0, 12, 100); + // sa.Log.Debug($"青之波潮{drawCount} 打两侧 绘图完毕"); + break; + + case 607: + sa.DrawRect(new Vector3(100, 0, 78), 0, 15000, $"青之波潮{drawCount} 打中间", 0, 12, 100); + // sa.Log.Debug($"青之波潮{drawCount} 打中间 绘图完毕"); + break; + + case 604: + sa.DrawCircle(new Vector3(100, 0, 78), 0, 15000, $"青之波潮{drawCount} 钢铁", 20); + // sa.Log.Debug($"青之波潮{drawCount} 钢铁 绘图完毕"); + break; + + case 605: + sa.DrawDonut(new Vector3(100, 0, 78), 0, 15000, $"青之波潮{drawCount} 月环", 60, 16); + // sa.Log.Debug($"青之波潮{drawCount} 月环 绘图完毕"); + break; + } + } + + [ScriptMethod(name: "青之多重波潮分摊绘图", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4518[345]|44608)$", "TargetIndex:1"], + userControl: true, suppress: 1000)] + public void 青之多重波潮分摊绘图(Event ev, ScriptAccessory sa) + { + _events[6].WaitOne(10000); + var drawCount = _numbers[5]; + if (drawCount != 3) return; + var isPartnerStack = _bools[5]; + List partnerJudge = [0, 11, 2, 13, 0, 11, 2, 13]; + + // 青之多重波,对目标分摊绘图(此处选择TH) + var myPartIdx = partnerJudge[sa.GetMyIndex()]; + sa.DrawFan(new Vector3(100, 0, 78), sa.Data.PartyList[2], 0, 20000, $"青之波潮分摊 H1", + (isPartnerStack ? 20f : 30f).DegToRad(), 0, 100, 0, isPartnerStack ? myPartIdx == partnerJudge[2] : myPartIdx < 10); + sa.DrawFan(new Vector3(100, 0, 78), sa.Data.PartyList[3], 0, 20000, $"青之波潮分摊 H2", + (isPartnerStack ? 20f : 30f).DegToRad(), 0, 100, 0, isPartnerStack ? myPartIdx == partnerJudge[3] : myPartIdx > 10); + + if (isPartnerStack) + { + sa.DrawFan(new Vector3(100, 0, 78), sa.Data.PartyList[0], 0, 20000, $"青之波潮分摊 MT", + (isPartnerStack ? 20f : 30f).DegToRad(), 0, 100, 0, myPartIdx == partnerJudge[0]); + sa.DrawFan(new Vector3(100, 0, 78), sa.Data.PartyList[1], 0, 20000, $"青之波潮分摊 ST", + (isPartnerStack ? 20f : 30f).DegToRad(), 0, 100, 0, myPartIdx == partnerJudge[1]); + } + // sa.Log.Debug($"青之 {(isPartnerStack ? "四" : "二")} 重波潮 范围绘图完毕"); + } + + [ScriptMethod(name: "青之多重波潮指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4516[78])$"], + userControl: true)] + public void 青之多重波潮指路(Event ev, ScriptAccessory sa) + { + _bools[7] = true; + _events[5].WaitOne(10000); + var isPartnerStack = _bools[5]; + + // 青之多重波,指路 + var myIndex = sa.GetMyIndex(); + var startIdx = _numbers[3]; + + var drawCount = _numbers[5]; + var drawTotalCount = _numbers[4]; + var drawIdx = (startIdx + drawCount) % drawTotalCount; + + var isInsideSafe = _aetherBlightRec[drawIdx] is 605 or 606; + + // var myPosIdx = myIndex % 4 + (isPartnerStack ? 0 : 2) + (isInsideSafe ? 4 : 0); + var myPosIdx = myIndex % 4 + (isInsideSafe ? 4 : 0); + if (!isPartnerStack && myPosIdx % 4 <= 2) myPosIdx += 2; + sa.DrawGuidance(_markerPos[myPosIdx], 0, 20000, $"青之波潮指路 #{drawCount}"); + + var str = _aetherBlightRec[drawIdx] switch + { + 604 => "钢铁", + 605 => "月环", + 606 => "打两侧", + 607 => "打中间", + _ => "未知", + }; + + // sa.Log.Debug($"青之 {(isPartnerStack ? "四" : "二")} 重波潮 {str}({_aetherBlightRec[drawIdx]}) 指路 #{drawCount} {_markerPos[myPosIdx]} 绘图完毕"); + } + + [ScriptMethod(name: "青之多重波潮指路后续", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4518[345]|44608)$", "TargetIndex:1"], + userControl: Debugging, suppress: 1000)] + public void 青之多重波潮指路后续(Event ev, ScriptAccessory sa) + { + if (!_bools[7]) return; + _events[6].WaitOne(10000); + + var isPartnerStack = _bools[5]; + + // 青之多重波,指路 + var myIndex = sa.GetMyIndex(); + var startIdx = _numbers[3]; + + var drawCount = _numbers[5]; + var drawTotalCount = _numbers[4]; + var drawIdx = (startIdx + drawCount) % drawTotalCount; + + if (drawCount >= drawTotalCount) return; + var isInsideSafe = _aetherBlightRec[drawIdx] is 605 or 606; + + // sa.Log.Debug($"删除青之波潮{drawCount-1} 指路"); + sa.Method.RemoveDraw($"青之波潮指路 #{drawCount-1}.*"); + + var myPosIdx = myIndex % 4 + (isPartnerStack ? 0 : 2) + (isInsideSafe ? 4 : 0); + sa.DrawGuidance(_markerPos[myPosIdx], 0, 20000, $"青之波潮指路 #{drawCount}"); + + var str = _aetherBlightRec[drawIdx] switch + { + 604 => "钢铁", + 605 => "月环", + 606 => "打两侧", + 607 => "打中间" + }; + + // sa.Log.Debug($"青之 {(isPartnerStack ? "四" : "二")} 重波潮 {str}({_aetherBlightRec[drawIdx]}) 指路 #{drawCount} {_markerPos[myPosIdx]} 绘图完毕"); + } + + [ScriptMethod(name: "青之多重波潮绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4518[345]|44608)$", "TargetIndex:1"], + userControl: Debugging, suppress: 1000)] + public async void 青之多重波潮绘图删除(Event ev, ScriptAccessory sa) + { + _events[6].WaitOne(10000); + if (!_bools[8]) return; + await Task.Delay(800); + + var drawCount = _numbers[5]; + if (drawCount != 4) return; + // _aetherBlightRec = []; + // _aetherBlightSourceRec = []; + // _events[4].Reset(); + // // sa.Log.Debug($"青之多重波潮判定,清空矩阵,绘图删除,释放锁_events[4]"); + RefreshParams(); + // sa.Log.Debug($"青之多重波潮判定,刷新参数"); + sa.Method.RemoveDraw($"青之波潮.*"); + } + + #endregion 青魂 Aether Blight + + #region 大十字 Grand Cross + + [ScriptMethod(name: "---- 大十字 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 大十字分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "大十字刷新参数", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44568)$"], + userControl: Debugging)] + public void 大十字刷新参数(Event ev, ScriptAccessory sa) + { + // sa.Log.Debug($"读条大十字,刷新参数"); + RefreshParams(); + } + + [ScriptMethod(name: "大十字集合提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44568)$"], + userControl: true)] + public void 大十字集合提示(Event ev, ScriptAccessory sa) + { + sa.Method.TextInfo($"场中集合,两轮引导", 5000, true); + } + + [ScriptMethod(name: "大十字衰减提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44570)$"], + userControl: true)] + public void 大十字衰减提示(Event ev, ScriptAccessory sa) + { + sa.Method.TextInfo($"场中集合,两轮引导,角落躲避", 5000, true); + } + + [ScriptMethod(name: "大十字激光", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(015[78])$"], + userControl: true)] + public void 大十字激光(Event ev, ScriptAccessory sa) + { + var obj1 = sa.GetById(ev.SourceId); + var obj2 = sa.GetById(ev.TargetId); + + sa.WriteInvisible(obj1); + sa.WriteInvisible(obj2); + + if (obj1 is null || obj2 is null) return; + + var isFast = obj1.DataId == 18761; + var startPos = obj1.Position.RotateAndExtend(Center, isFast ? 41f.DegToRad() : -153f.DegToRad(), 0); + var targetPos = obj2.Position.RotateAndExtend(Center, isFast ? 41f.DegToRad() : -153f.DegToRad(), 0); + + var dp = sa.DrawRect(startPos, targetPos, 0, 15000, + $"大十字激光 {ev.SourceId}", 0, 5, 40, draw: false); + dp.Color = new Vector4(1, 0, 0, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + // sa.Log.Debug($"预测大十字激光 {(isFast ? "快" : "慢")} {ev.SourceId},{startPos} 到 {targetPos},绘图"); + } + + [ScriptMethod(name: "大十字激光消失", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(4541)$"], + userControl: Debugging)] + public void 大十字激光消失(Event ev, ScriptAccessory sa) + { + // sa.Log.Debug($"魂块{ev.TargetId}开始转动,大十字激光消失"); + sa.Method.RemoveDraw($"大十字激光 {ev.TargetId}"); + } + + [ScriptMethod(name: "大十字点名记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44572)$"], + userControl: Debugging)] + public void 大十字点名记录(Event ev, ScriptAccessory sa) + { + lock (_numbers) + { + _numbers[8]++; + _bools[9] |= ev.TargetId == sa.Data.Me; + if (_numbers[8] < 4) return; + _events[7].Set(); + // sa.Log.Debug($"记录点名完毕,玩家{(_bools[9] ? "不踩塔" : "踩塔")}"); + } + } + + [ScriptMethod(name: "大十字踩塔指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44573)$"], + userControl: true, suppress: 5000)] + public void 大十字踩塔指路(Event ev, ScriptAccessory sa) + { + _events[7].WaitOne(10000); + + var pos = ev.SourcePosition.GetRadian(Center).RadianToRegion(8, 0, true, false); + var isDiagTower = pos % 2 == 1; + // sa.Log.Debug($"检测到塔方位 {pos}, {(isDiagTower ? "是" : "不是")} 斜点塔"); + + List rot = [2, 1, 3, 0]; + var myIndex = sa.GetMyIndex() % 4; + + var tower = !_bools[9]; + if (tower) + { + sa.DrawGuidance(new Vector3(100, 0, 105.5f).RotateAndExtend(Center, (rot[myIndex] * 90f + (isDiagTower ? 45f : 0f)).DegToRad(), 0), + 0, 5000, $"大十字踩塔分散"); + // sa.Log.Debug($"踩塔指路完毕"); + } + else + { + sa.DrawGuidance(new Vector3(100, 0, 105.5f).RotateAndExtend(Center, (rot[myIndex] * 90f + (isDiagTower ? 0f : 45f)).DegToRad(), 0), + 0, 5000, $"大十字踩塔分散"); + // sa.Log.Debug($"分散指路完毕"); + } + + } + + [ScriptMethod(name: "大十字踩塔指路删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44573)$"], + userControl: Debugging, suppress: 5000)] + public void 大十字踩塔指路删除(Event ev, ScriptAccessory sa) + { + _bools[9] = false; + _numbers[8] = 0; + _events[7].Reset(); + // sa.Log.Debug($"踩塔判定,删除大十字踩塔指路"); + sa.Method.RemoveDraw($"大十字踩塔分散"); + } + + #endregion 大十字 GrandCross + + #region 小怪 Mobs + + [ScriptMethod(name: "---- 小怪 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 小怪分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "小怪压溃", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4457[89]|44584)$"], + userControl: true)] + public void 小怪压溃(Event ev, ScriptAccessory sa) + { + var dp = sa.DrawRect(ev.SourceId, ev.ActionId is 44579 or 44584 ? 0 : ev.TargetId, 0, 3000, + $"小怪{ev.SourceId}压溃", 0, 6, 24, draw: false); + dp.Color = ev.ActionId is 44579 or 44584 ? sa.Data.DefaultDangerColor : new Vector4(1, 0, 0, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "小怪压溃绘图删除1", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4457[89]|44584)$"], + userControl: Debugging)] + public void 小怪压溃绘图删除1(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"小怪{ev.SourceId}压溃"); + } + + [ScriptMethod(name: "小怪压溃绘图删除2", eventType: EventTypeEnum.CancelAction, eventCondition: ["ActionId:regex:^(4457[89]|44584)$"], + userControl: Debugging)] + public void 小怪压溃绘图删除2(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"小怪{ev.SourceId}压溃"); + } + + [ScriptMethod(name: "转场刷新参数", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44580)$"], + userControl: Debugging)] + public void 转场刷新参数(Event ev, ScriptAccessory sa) + { + // sa.Log.Debug($"读条转场永恒之暗,刷新参数"); + RefreshParams(); + } + + #endregion 小怪 Mobs + + #region 引导之翼 The End's Embrace + + [ScriptMethod(name: "---- 圆环之理 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 圆环之理分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "场边暗之巨腕拖入", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44591)$"], + userControl: true)] + public void 场边暗之巨腕拖入(Event ev, ScriptAccessory sa) + { + sa.DrawRect(ev.SourceId, 0, 5000, $"场边暗之巨腕{ev.SourceId}拖入", 0, 10, 36); + } + + [ScriptMethod(name: "场边暗之巨腕危险区记录", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44591)$"], + userControl: Debugging)] + public void 场边暗之巨腕危险区记录(Event ev, ScriptAccessory sa) + { + lock (_numbers) + { + var rowIdx = (int)Math.Floor((ev.SourcePosition.Z - 79) / 10); + _numbers[10] += (int)Math.Pow(10, rowIdx - 1); + // sa.Log.Debug($"场边暗之巨腕 第 {rowIdx} 行危险(1起),计数值 {_numbers[10]}"); + } + } + + [ScriptMethod(name: "场边暗之巨腕拖入删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44591)$"], + userControl: Debugging)] + public void 场边暗之巨腕拖入删除(Event ev, ScriptAccessory sa) + { + lock (_numbers) + { + var rowIdx = (int)Math.Floor((ev.SourcePosition.Z - 79) / 10); + _numbers[10] -= (int)Math.Pow(10, rowIdx - 1); + // sa.Log.Debug($"场边暗之巨腕 第 {rowIdx} 行判定,绘图删除,计数值 {_numbers[10]}"); + sa.Method.RemoveDraw($"场边暗之巨腕{ev.SourceId}拖入"); + } + } + + [ScriptMethod(name: "引导之翼指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44597)$"], + userControl: true)] + public void 引导之翼指路(Event ev, ScriptAccessory sa) + { + // 寻找安全区 + if (_numbers[10] == 0) return; // 无手,随便放 + int safeRow = 0; + for (int i = 0; i < 3; i++) + { + if (_numbers[10].GetDecimalDigit(i + 1) != 0) continue; + safeRow = i + 1; + // sa.Log.Debug($"找到暗之巨腕安全行 {safeRow}"); + } + + var baitPos = new Vector3(88f, 0, 87f); + var myIndex = sa.GetMyIndex(); + + if (myIndex is 2 or 3 or 6 or 7) + baitPos += new Vector3(0, 0, 6); + if (myIndex is 4 or 5 or 6 or 7) + baitPos += new Vector3(6, 0, 0); + if (myIndex is 1 or 3 or 5 or 7) + baitPos = baitPos.FoldPointHorizon(Center.X); + + baitPos += new Vector3(0, 0, (safeRow - 1) * 10); + sa.DrawGuidance(baitPos, 0, 4000, $"引导之翼指路"); + sa.DrawGuidance(baitPos, new Vector3(100, 0, 80 + safeRow * 10), + 0, 4000, $"引导之翼后续指路预备", isSafe: false); + sa.DrawGuidance(new Vector3(100, 0, 80 + safeRow * 10), + 4000, 4000, $"引导之翼后续指路", isSafe: true); + } + + [ScriptMethod(name: "引导之翼指路删除", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44567)$"], + userControl: Debugging, suppress: 2000)] + public void 引导之翼指路删除(Event ev, ScriptAccessory sa) + { + // sa.Log.Debug($"黑手释放压溃,引导之翼相关指路删除"); + sa.Method.RemoveDraw($"引导之翼.*"); + } + + #endregion 引导之翼 The End's Embrace + + #region 青之环波 Circle of Lives + + [ScriptMethod(name: "---- 青之环波 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 青之环波分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "青之环波绘图", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44600)$"], + userControl: true)] + public void 青之环波绘图(Event ev, ScriptAccessory sa) + { + sa.DrawDonut(ev.SourceId, _judging_CoL ? 2500 : 0, _judging_CoL ? 4500 : 7000, + $"青之环波{ev.SourceId}", 50, 3); + _judging_CoL = true; + } + + [ScriptMethod(name: "青之环波绘图删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44600)$"], + userControl: Debugging)] + public void 青之环波绘图删除(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"青之环波{ev.SourceId}"); + } + + #endregion 青之环波 Circle of Lives + + #region 群体恐慌 Mass Macabre + + [ScriptMethod(name: "---- 群体恐慌 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void 群体恐慌分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "群体恐慌刷新参数", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44595)$"], + userControl: Debugging)] + public void 群体恐慌刷新参数(Event ev, ScriptAccessory sa) + { + // sa.Log.Debug($"读条群体恐慌,刷新参数"); + RefreshParams(); + } + + [ScriptMethod(name: "群体恐慌踩塔", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(44595)$"], + userControl: true)] + public void 群体恐慌塔1(Event ev, ScriptAccessory sa) + { + var myIndex = sa.GetMyIndex(); + var isMelee = myIndex is 0 or 1 or 4 or 5; + // sa.Log.Debug($"群体恐慌塔1 指路"); + sa.DrawGuidance(new Vector3(100, 0, isMelee ? 94 : 106), 0, 10000, $"群体恐慌塔1"); + } + + [ScriptMethod(name: "群体恐慌塔 后续", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(44819)$"], + userControl: Debugging)] + public void 群体恐慌踩塔计数(Event ev, ScriptAccessory sa) + { + if (ev.TargetId != sa.Data.Me) return; + _numbers[7]++; + // sa.Log.Debug($"完成群体恐慌踩塔,计数 {_numbers[7]}"); + sa.Method.RemoveDraw($"群体恐慌塔{_numbers[7]}.*"); + + var myIndex = sa.GetMyIndex(); + switch (_numbers[7]) + { + case 1: + var mySecondTower = new Vector3(85f, 0, 103f); + if (myIndex is 4 or 5 or 6 or 7) mySecondTower = mySecondTower.FoldPointVertical(Center.Z); + if (myIndex is 1 or 3 or 4 or 5) mySecondTower = mySecondTower.FoldPointHorizon(Center.X); + // sa.Log.Debug($"向 塔#2 {mySecondTower} 指路"); + sa.DrawGuidance(mySecondTower, 0, 4000, $"群体恐慌塔2预备", isSafe: false); + sa.DrawGuidance(mySecondTower, 4000, 6000, $"群体恐慌塔2", isSafe: true); + break; + case 2: + var myThirdTower = myIndex switch + { + 2 or 6 or 7 => new Vector3(93.5f, 0, 112f), + 3 or 4 or 5 => new Vector3(93.5f, 0, 112f).PointCenterSymmetry(Center), + 0 => new Vector3(82.5f, 0, 85.5f), + 1 => new Vector3(82.5f, 0, 85.5f).FoldPointHorizon(Center.X), + }; + // sa.Log.Debug($"向 塔#3 {myThirdTower} 或 死刑点 指路"); + sa.DrawGuidance(myThirdTower, 0, 4000, $"群体恐慌塔3预备", isSafe: false); + sa.DrawGuidance(myThirdTower, 4000, 6000, $"群体恐慌塔3", isSafe: true); + break; + case 3: + // 一般来说,双T不会到达这一步 + var myFourthTower = myIndex switch + { + 2 or 6 or 7 => new Vector3(93.5f, 0, 112f).FoldPointHorizon(Center.X), + 3 or 4 or 5 => new Vector3(93.5f, 0, 112f).FoldPointVertical(Center.Z), + 0 => new Vector3(82.5f, 0, 85.5f), + 1 => new Vector3(82.5f, 0, 85.5f).FoldPointHorizon(Center.X), + }; + // sa.Log.Debug($"向 塔#4 {myFourthTower} 或 死刑点 指路"); + sa.DrawGuidance(myFourthTower, 0, 4000, $"群体恐慌塔4预备", isSafe: false); + sa.DrawGuidance(myFourthTower, 4000, 6000, $"群体恐慌塔4", isSafe: true); + break; + default: + break; + } + } + + #endregion 群体恐慌 Mass Macabre + +} + +#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 class IbcHelper +{ + public static IGameObject? GetById(this ScriptAccessory sa, ulong gameObjectId) + { + return sa.Data.Objects.SearchById(gameObjectId); + } + +} +#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 +{ + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入位置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]; + } +} +#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 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); + + /// + /// 赋予输入的dp以ownerId为源的仇恨顺序绘图 + /// + /// + /// 仇恨顺序,从1开始 + /// + public static DrawPropertiesEdit SetOwnersEnmityOrder(this DrawPropertiesEdit self, uint orderIdx) + { + self.TargetResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + self.TargetOrderIndex = 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; + } +} + +#endregion 绘图函数 + +#region 特殊函数 + +public static class SpecialFunc +{ + [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); + + public static unsafe void WriteInvisible(this ScriptAccessory sa, IGameObject? actor) + { + try + { + // Invisibility = 0x00_00_00_02, + *ActorDrawState(actor!) |= DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error(e.ToString()); + throw; + } + } + + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor) + { + try + { + *ActorDrawState(actor!) &= ~DrawState.Invisibility; + } + catch (Exception e) + { + sa.Log.Error(e.ToString()); + throw; + } + } +} + +#endregion 特殊函数 + +#endregion 函数集 + diff --git a/lianying1997/07-Dawntrail/QueenEternal/QueenEternalEx.cs b/lianying1997/07-Dawntrail/QueenEternal/QueenEternalEx.cs new file mode 100644 index 0000000..902ad62 --- /dev/null +++ b/lianying1997/07-Dawntrail/QueenEternal/QueenEternalEx.cs @@ -0,0 +1,2333 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; + +namespace UsamisKodakku.Script._07_DawnTrail.QueenEternal; + +[ScriptType(name: Name, territorys: [1243], guid: "45fff289-e23d-41ab-9039-71cd310668e4", + version: Version, author: "Usami", note: NoteStr)] + +// ^(?!.*((武僧|机工士|龙骑士|武士|忍者|蝰蛇剑士|钐镰客|舞者|吟游诗人|占星术士|贤者|学者|(朝日|夕月)小仙女|炽天使|白魔法师|战士|骑士|暗黑骑士|绝枪战士|绘灵法师|黑魔法师|青魔法师|召唤师|宝石兽|亚灵神巴哈姆特|亚灵神不死鸟|迦楼罗之灵|泰坦之灵|伊弗利特之灵|后式自走人偶)\] (Used|Cast))).*35501.*$ + +public class QueenEternalEx +{ + const string NoteStr = + """ + 绝对君权为正攻,若逃课请忽略核爆远离指路箭头。 + 针对土阶段涉及优先级的踩塔判断,可以开启用户设置中“土阶段四人塔智能优先级判断”。 + 若不使用此功能,请确认小队列表排序正确(特别关注D1/D2),避免踩塔指路错误。 + 鸭门。 + """; + + private const string Name = "QueenEternalEx [永恒女王忆想歼灭战]"; + private const string Version = "0.0.0.9"; + private const string DebugVersion = "a"; + private const string Note = "初版完成"; + + [UserSetting("Debug模式,非开发用请关闭")] + public static bool DebugMode { get; set; } = false; + [UserSetting("土阶段四人塔智能优先级判断")] + public static bool IntelligentPriorTowerMode { get; set; } = true; + [UserSetting("站位提示圈绘图-普通颜色")] + public static ScriptColor PosColorNormal { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 1.0f, 1.0f) }; + [UserSetting("站位提示圈绘图-玩家站位颜色")] + public static ScriptColor PosColorPlayer { get; set; } = new ScriptColor { V4 = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) }; + [UserSetting("地火爆炸区颜色")] + public ScriptColor exflareColor { get; set; } = new ScriptColor { V4 = new Vector4(0f, 1.0f, 1.0f, 1.0f) }; + [UserSetting("地火预警区颜色")] + public ScriptColor exflareWarnColor { get; set; } = new ScriptColor { V4 = new Vector4(0f, 0.5f, 1.0f, 1.0f) }; + + private List _drawn = new bool[20].ToList(); // 绘图记录 + private QueenEternalPhase _phase = QueenEternalPhase.Init; + private static uint _bossId = 0; + + private static readonly Vector3 Center = new Vector3(100, 0, 100); + private static readonly Vector3 CenterWind = new Vector3(100, 0, 92.5f); + private static readonly Vector3 CenterEarth = new Vector3(100, 0, 94f); + private static readonly Vector3 CenterIce = new Vector3(100, 0, 100); + private static readonly Vector3 GravityFieldLeft = new Vector3(92f, 0, 94f); + private List _manualResetEvents = Enumerable.Repeat(new ManualResetEvent(false), 20).ToList(); + private List _autoResetEvents = Enumerable.Repeat(new AutoResetEvent(false), 20).ToList(); + private List _earthPhaseTarget = new bool[8].ToList(); // 绘图记录 + private List _intelligentPriority = Enumerable.Repeat(0, 8).ToList(); + private bool _intelligentPriorityValid = true; + + private bool _isFlareTarget = false; // 绝对君权核爆目标 + private Vector3 _sourceIceDartPos = new Vector3(0, 0, 0); // 与玩家连线的目标冰柱位置 + private int _myIceBridgeIdx = 0; // 冰阶段走的冰桥 + private List _iceRushRangeDrawn = new bool[8].ToList(); // 冰柱突刺是否被绘图 + private int _raisedTributeNum = 0; // 接线分摊,分摊次数 + + + public void Init(ScriptAccessory accessory) + { + // DebugMsg($"Init {Name} v{Version}{DebugVersion} Success.\n{Note}", accessory); + _phase = QueenEternalPhase.Init; + _drawn = new bool[20].ToList(); + _bossId = 0; + _manualResetEvents = Enumerable.Repeat(new ManualResetEvent(false), 20).ToList(); + _autoResetEvents = Enumerable.Repeat(new AutoResetEvent(false), 20).ToList(); + + _earthPhaseTarget = new bool[8].ToList(); // 绘图记录 + _isFlareTarget = false; // 绝对君权核爆目标 + _sourceIceDartPos = new Vector3(0, 0, 0); // 与玩家连线的目标冰柱位置 + _myIceBridgeIdx = 0; // 冰阶段走的冰桥 + _iceRushRangeDrawn = new bool[8].ToList(); // 冰柱突刺是否被绘图 + _raisedTributeNum = 0; // 接线分摊,分摊次数 + _exaflareShown = false; // 是否显示地火 + + _intelligentPriority = Enumerable.Repeat(0, 8).ToList(); // 四人塔智能优先级模式权重 + _intelligentPriorityValid = true; // 四人塔智能优先级模式是否合理 + + accessory.Method.MarkClear(); + accessory.Method.RemoveDraw(".*"); + } + + public static void DebugMsg(string str, ScriptAccessory accessory) + { + if (!DebugMode) return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + [ScriptMethod(name: "随时DEBUG用", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:=TST"], userControl: false)] + public void EchoDebug(Event @event, ScriptAccessory accessory) + { + if (!DebugMode) return; + // ---- DEBUG CODE ---- + + // -- DEBUG CODE END -- + } + + #region 以太税 + + [ScriptMethod(name: "记录bossId", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40972"], userControl: false)] + public void BossIdRecord(Event @event, ScriptAccessory accessory) + { + if (_bossId != 0) return; + _bossId = @event.SourceId(); + } + + [ScriptMethod(name: "---- 《以太税》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_Aethertithe(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "以太税分摊区提示", eventType: EventTypeEnum.EnvControl, eventCondition: ["Id:regex:^((04|08|10)000100)$", "Index:00000000"], userControl: true)] + public void AethertitheStackPositionHint(Event @event, ScriptAccessory accessory) + { + var id = @event.Id(); + List safePlace = [true, true, true]; + const uint left = 0x04000100; + const uint middle = 0x08000100; + const uint right = 0x10000100; + var idx = id switch + { + left => 0, + middle => 1, + _ => 2 + }; + + safePlace[idx] = false; + var myIndex = accessory.GetMyIndex(); + var playerPos = myIndex % 2 == 0 ? safePlace.IndexOf(true) : safePlace.LastIndexOf(true); + + List dirPos = [new Vector3(80, 0, 100), new Vector3(100, 0, 120), new Vector3(120, 0, 100)]; + List rot = [-60f.DegToRad(), 0, 60f.DegToRad()]; + for (var i = 0; i < 3; i++) + { + var startPos = new Vector3(100, 0, 80); + var dp = accessory.DrawGuidance(startPos, dirPos[i], 0, 6000, $"分摊指示{i}", 0, 2f); + dp.Color = i == playerPos ? PosColorPlayer.V4.WithW(3f) : PosColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + var dp0 = accessory.DrawFan(_bossId, 70f.DegToRad(), rot[idx], 100, 0, 0, 20000, $"以太税{idx}"); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp0); + + // var stackPosStr = playerPos switch + // { + // 0 => "左侧", + // 1 => "中间", + // _ => "右侧" + // }; + // accessory.Method.TextInfo($"{stackPosStr}分摊", 6000); + } + + [ScriptMethod(name: "以太税删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4097[456])"], userControl: false)] + public void AethertitheStackRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + #endregion 以太税 + + #region 左右刀 + + [ScriptMethod(name: "---- 《左右刀》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_LegitimateForce(Event @event, ScriptAccessory accessory) + { + } + + private bool _legitimateForce; + [ScriptMethod(name: "左右刀提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4099[02])"], userControl: true)] + public void LegitimateForce(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var aid = @event.ActionId(); + _legitimateForce = true; + const uint attackLeftHand = 40992; + + var dp = accessory.DrawLeftRightCleave(sid, aid == attackLeftHand, 3000, 5000, $"左右刀1"); + dp.Scale = new Vector2(150f); + if (_phase is QueenEternalPhase.Earth) + { + dp.Offset = new(0, -3.5f, 0); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp); + } + else + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "左右刀第二段", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4099[02])"], userControl: false)] + public void LegitimateForceSecond(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var aid = @event.ActionId(); + if (!_legitimateForce) return; + _legitimateForce = false; + accessory.Method.RemoveDraw($"左右刀1"); + const uint attackLeftHand = 40992; + var dp = accessory.DrawLeftRightCleave(sid, aid != attackLeftHand, 0, 8000, $"左右刀2"); + dp.Scale = new Vector2(150f); + if (_phase == QueenEternalPhase.Earth) + { + dp.Offset = new(0, -3.5f, 0); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp); + } + else + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } + + [ScriptMethod(name: "左右刀第二段消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4099[34])"], userControl: false)] + public void LegitimateForceRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"左右刀2"); + } + + #endregion + + #region 风阶段 + + [ScriptMethod(name: "---- 《风阶段》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_LawsofWind(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "风阶段转换", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40995"], userControl: false)] + public void LawsofWindPhaseChange(Event @event, ScriptAccessory accessory) + { + _phase = QueenEternalPhase.Wind; + accessory.DebugMsg($"当前阶段为:{_phase}", DebugMode); + _autoResetEvents[0].Set(); + } + + [ScriptMethod(name: "风分摊提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:40995"], userControl: true)] + public void AeroquellStackPositionHint(Event @event, ScriptAccessory accessory) + { + _autoResetEvents[0].WaitOne(); + if (_phase != QueenEternalPhase.Wind) return; + var myIndex = accessory.GetMyIndex(); + + var stackPos = new Vector3(94f, 0, 89.5f); + if (myIndex % 2 == 1) + stackPos = stackPos.FoldPointHorizon(CenterWind.X).FoldPointVertical(CenterWind.Z); + var dp = accessory.DrawGuidance(stackPos, 0, 9000, $"风分摊"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + // var stackPosStr = myIndex % 2 == 0 ? "左上靠中" : "右下靠中"; + // accessory.Method.TextInfo($"{stackPosStr}分摊", 4000); + // accessory.Method.TTS($"{stackPosStr}分摊"); + } + + [ScriptMethod(name: "拉线集合提示", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:0146"], userControl: true)] + public void MissingLinkStackFirst(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Wind) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + + var dp = accessory.DrawGuidance(CenterWind, 0, 4000, $"拉线集合"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + accessory.Method.TextInfo($"场中集合", 4000); + // accessory.Method.TTS($"场中集合"; + } + + [ScriptMethod(name: "拉线集合提示删除", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3587)$"], userControl: false)] + public void MissingLinkStackRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"拉线集合"); + } + + [ScriptMethod(name: "拉线方向提示", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(3587)$"], userControl: true)] + public void MissingLineTether(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Wind) return; + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + var myIndex = accessory.GetMyIndex(); + + var tetherDes = new Vector3(88f, 0, 104f); + if (myIndex >= 4) + tetherDes = tetherDes.FoldPointHorizon(CenterWind.X).FoldPointVertical(CenterWind.Z); + var dp = accessory.DrawGuidance(tetherDes, 0, 6000, $"拉线位置"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + + // var tetherDesStr = myIndex <= 3 ? $"左下" : "右上"; + // accessory.Method.TextInfo($"{tetherDesStr}拉线", 3000); + // accessory.Method.TTS($"{tetherDesStr}拉线"); + } + + [ScriptMethod(name: "拉线方向提示删除", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:regex:^(3587)$"], userControl: false)] + public void MissingLineTetherRemove(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + accessory.Method.RemoveDraw($"拉线位置"); + _autoResetEvents[0].Set(); + } + + private bool _windChargeToLeft; + [ScriptMethod(name: "风击退方向记录", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(4189|4190)$"], userControl: false)] + public void WindOfChangeRecord(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Wind) return; + var tid = @event.TargetId(); + var stid = @event.StatusId(); + if (tid != accessory.Data.Me) return; + const uint eastWindOfCharge = 4189; + _windChargeToLeft = stid == eastWindOfCharge; // 向左击退 + accessory.DebugMsg($"记录到{stid}, {(_windChargeToLeft ? "向左击退" : "向右击退")}", DebugMode); + } + + private List _windChargeGuidancePosition = [new(0, 0, 0), new(0, 0, 0), new(0, 0, 0)]; + private bool _windGuidance; + [ScriptMethod(name: "风击退指路", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4099[02])$"], userControl: true)] + public void WindOfChangeGuidancePart1(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Wind) return; + _autoResetEvents[0].WaitOne(); + var aid = @event.ActionId(); + var myIndex = accessory.GetMyIndex(); + _windGuidance = true; + + const float deltaZ = 10.5f; // 上半场与下半场Z轴差 + const float centerDeltaX = 1f; // 躲左右刀X轴差 + const float edgeX = 12f; // 场中X与风场地边缘X差 + + // 是TN在下半场,所以增加dz。此处以上半场为基准 + var dz = myIndex <= 3 ? new Vector3(0, 0, deltaZ) : new Vector3(0, 0, -deltaZ); + + // 根据左右刀顺序决定dx偏置 + const uint attackRightFirst = 40992; + var dx = aid == attackRightFirst ? new Vector3(-centerDeltaX, 0, 0) : new Vector3(centerDeltaX, 0, 0); + + // 根据击退方向决定dxWind偏置 + var dxWind = _windChargeToLeft ? new Vector3(edgeX, 0, 0) : new Vector3(-edgeX, 0, 0); + + var targetPos1 = CenterWind + dz + dx; + var targetPos2 = CenterWind + dz - dx; + var targetPos3 = CenterWind + dz + dxWind; + _windChargeGuidancePosition = [targetPos1, targetPos2, targetPos3]; + + var dp1 = accessory.DrawGuidance(_windChargeGuidancePosition[0], 0, 8000, $"风击退1"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + var dp12 = accessory.DrawGuidance(_windChargeGuidancePosition[0], _windChargeGuidancePosition[1], 0, 8000, $"风击退12"); + dp12.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp12); + } + + [ScriptMethod(name: "风击退指路2", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4099[02])$"], userControl: false)] + public void WindOfChangeGuidancePart2(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Wind) return; + accessory.Method.RemoveDraw($"风击退1"); + accessory.Method.RemoveDraw($"风击退12"); + + if (!_windGuidance) return; + var dp2 = accessory.DrawGuidance(_windChargeGuidancePosition[1], 0, 6000, $"风击退2"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + var dp23 = accessory.DrawGuidance(_windChargeGuidancePosition[1], _windChargeGuidancePosition[2], 0, 8000, $"风击退23"); + dp23.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp23); + } + + [ScriptMethod(name: "风击退指路3", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4099[34])$"], userControl: false)] + public void WindOfChangeGuidancePart3(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Wind) return; + accessory.Method.RemoveDraw($"风击退2"); + accessory.Method.RemoveDraw($"风击退23"); + + if (!_windGuidance) return; + var dp3 = accessory.DrawGuidance(_windChargeGuidancePosition[2], 0, 6000, $"风击退3"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp3); + } + + [ScriptMethod(name: "风击退指路删除", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:regex:^(4189|4190)$"], userControl: false)] + public void WindOfChangeGuidanceRemove(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Wind) return; + if (@event.TargetId() != accessory.Data.Me) return; + accessory.Method.RemoveDraw($".*"); + _windGuidance = false; + } + + #endregion 风阶段 + + #region 分治法 + [ScriptMethod(name: "---- 《分治法》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_DivideAndConquer(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "站位提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(40983)$"], userControl: true)] + public void DivideAndConquerPosition(Event @event, ScriptAccessory accessory) + { + var myIndex = accessory.GetMyIndex(); + var sid = @event.SourceId(); + var spos = @event.SourcePosition(); + + List rot = [-85f.DegToRad(), 85f.DegToRad(), -10f.DegToRad(), 10f.DegToRad(), -60f.DegToRad(), 60f.DegToRad(), -35f.DegToRad(), 35f.DegToRad()]; + for (var i = 0; i < 8; i++) + { + var dp = accessory.DrawRect(sid, 2f, 60f, 0, 10000, $"分治法{i}"); + dp.Rotation = rot[i]; + dp.Color = i == myIndex ? PosColorPlayer.V4.WithW(3f) : PosColorNormal.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + // var standPosStr = myIndex switch + // { + // 0 => "左上", + // 1 => "右上", + // 2 => "场中偏左", + // 3 => "场中偏右", + // 4 => "左2", + // 5 => "右2", + // 6 => "左3", + // 7 => "右3", + // _ => "???" + // }; + // accessory.Method.TextInfo($"{standPosStr}引导后躲开", 4000); + // accessory.Method.TTS($"{standPosStr}引导后躲开"); + } + + [ScriptMethod(name: "分治法第二段", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(30505)$"], userControl: true)] + public void DivideAndConquerDelayCast(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + + var dp = accessory.DrawRect(sid, 5, 60, 0, 3000, $"分治法第二段{sid}"); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + #endregion 分治法 + + #region 土阶段 + + [ScriptMethod(name: "---- 《土阶段》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_LawsofEarth(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "土阶段转换", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:41000"], userControl: false)] + public void LawsofEarthPhaseChange(Event @event, ScriptAccessory accessory) + { + _phase = QueenEternalPhase.Earth; + accessory.DebugMsg($"当前阶段为:{_phase}", DebugMode); + } + + [ScriptMethod(name: "第一轮踩塔提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(4099[02])$"], userControl: true)] + public void LawsofEarthTower8(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Earth) return; + + // 左右浮空阵 + var targetPos1 = Enumerable.Repeat(new Vector3(0, 0, 0), 2).ToList(); + targetPos1[0] = GravityFieldLeft; + targetPos1[1] = GravityFieldLeft.FoldPointHorizon(CenterEarth.X); + + // 8人对应踩塔位置 + List targetPos2 = Enumerable.Repeat(new Vector3(0, 0, 0), 8).ToList(); + targetPos2[0] = new Vector3(94f, 0, 88f); + targetPos2[2] = targetPos2[0].FoldPointVertical(CenterEarth.Z); + targetPos2[4] = targetPos2[0].FoldPointHorizon(GravityFieldLeft.X); + targetPos2[6] = targetPos2[4].FoldPointVertical(CenterEarth.Z); + targetPos2[1] = targetPos2[0].FoldPointHorizon(CenterEarth.X); + targetPos2[3] = targetPos2[2].FoldPointHorizon(CenterEarth.X); + targetPos2[5] = targetPos2[4].FoldPointHorizon(CenterEarth.X); + targetPos2[7] = targetPos2[6].FoldPointHorizon(CenterEarth.X); + + var myIndex = accessory.GetMyIndex(); + var idx1 = myIndex % 2 == 0 ? 0 : 1; + + var dp1 = accessory.DrawGuidance(CenterEarth, targetPos1[idx1], 0, 11000, $"踩浮空阵{idx1}"); + var dp2 = accessory.DrawGuidance(targetPos1[idx1], targetPos2[myIndex], 0, 11000, $"踩塔{myIndex}"); + dp1.Color = accessory.Data.DefaultDangerColor; + dp2.Color = accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + + dp1 = accessory.DrawGuidance(CenterEarth, targetPos1[idx1], 11000, 5000, $"踩浮空阵{idx1}"); + dp2 = accessory.DrawGuidance(targetPos1[idx1], targetPos2[myIndex], 11000, 5000, $"踩塔{myIndex}"); + dp1.Color = accessory.Data.DefaultSafeColor; + dp2.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + + // var idxStr = myIndex switch + // { + // 0 => "左上内", + // 1 => "左下内", + // 2 => "右上内", + // 3 => "右下内", + // 4 => "左上外", + // 5 => "左下外", + // 6 => "右上外", + // 7 => "右下外", + // _ => "???" + // }; + // + // accessory.Method.TextInfo($"准备踩{idxStr}塔", 4000); + // accessory.Method.TTS($"准备踩{idxStr}塔"); + } + + [ScriptMethod(name: "踩塔提示删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4100[12])$"], userControl: false)] + public void LawsofEarthTower8Remove(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Earth) return; + accessory.Method.RemoveDraw($".*"); + } + + [ScriptMethod(name: "踩塔优先级判断", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4100[12])$"], userControl: false)] + public void Tower4PriorityJudge(Event @event, ScriptAccessory accessory) + { + // 根据八人塔相对位置判断 + if (_phase != QueenEternalPhase.Earth) return; + if (_drawn[3]) return; + _drawn[3] = true; + if (!IntelligentPriorTowerMode) return; + + for (var i = 0; i < accessory.Data.PartyList.Count; i++) + { + var str = $"玩家设定{accessory.GetPlayerJobByIndex(i)}的信息:"; + var id = accessory.Data.PartyList[i]; + var chara = accessory.GetById(id); + + if (chara == null || chara.IsDead) + { + _intelligentPriorityValid = false; + accessory.DebugMsg($"发现存在角色阵亡或不存在,智能判断失效。", DebugMode); + return; + } + + if (accessory.Data.MyObject is null) return; + if (!accessory.Data.MyObject.IsDps()) + { + _intelligentPriority[i] += 1; + str += $"为TN(+1)"; + } + else + { + str += $"为DPS(+0)"; + } + + if (chara.Position.X > CenterEarth.X) + { + _intelligentPriority[i] += 100; + str += $",在右半场(+100)"; + } + else + { + str += $",在左半场(+0)"; + } + + if (chara.Position.Z > CenterEarth.Z) + { + _intelligentPriority[i] += 10; + str += $",在下半场(+10)"; + } + else + { + str += $",在上半场(+0)"; + } + accessory.DebugMsg($"{str} = {_intelligentPriority[i]}分。", DebugMode); + } + + // 判断是否合法 + // 1. 右半场4人 (>= 100) + // 2. 下半场4人 (% 100 >= 10) + if (_intelligentPriority.Count(x => x >= 100) != 4) + { + _intelligentPriorityValid = false; + accessory.DebugMsg($"右半场人数不为4人,智能判断失效", DebugMode); + return; + } + + if (_intelligentPriority.Count(x => x % 100 >= 10) != 4) + { + _intelligentPriorityValid = false; + accessory.DebugMsg($"下半场人数不为4人,智能判断失效", DebugMode); + return; + } + } + + [ScriptMethod(name: "大圈提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(41004)$"], userControl: true)] + public void LawsofEarthDefamation(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Earth) return; + var tid = @event.TargetId(); + var tidx = accessory.GetPlayerIdIndex(tid); + HandleEarthPhaseTarget(tidx, accessory, $"大圈目标"); + + if (tid != accessory.Data.Me) return; + // 建立在分组正常的情况下,一左一右点名 + + // 左右浮空阵 + var targetPos1 = Enumerable.Repeat(new Vector3(0, 0, 0), 2).ToList(); + targetPos1[0] = GravityFieldLeft; + targetPos1[1] = GravityFieldLeft.FoldPointHorizon(CenterEarth.X); + + var targetPos2 = Enumerable.Repeat(new Vector3(0, 0, 0), 2).ToList(); + targetPos2[0] = new Vector3(82f, 0, 95f); + targetPos2[1] = targetPos2[0].FoldPointHorizon(CenterEarth.X); + + var myIndex = accessory.GetMyIndex(); + var idx = myIndex % 2 == 0 ? 0 : 1; + var bias = myIndex % 4 <= 1 ? new Vector3(0, 0, -5) : new Vector3(0, 0, 5); + + // 偷个懒,不从自己身上指路,做一个bias意为先踩塔后 + var dp1 = accessory.DrawGuidance(targetPos1[idx] + bias, targetPos1[idx], 0, 8000, + $"踩浮空阵{idx}"); + var dp2 = accessory.DrawGuidance(targetPos1[idx], targetPos2[idx], 0, 8000, $"放圈{idx}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + } + + [ScriptMethod(name: "扇形引导提示", eventType: EventTypeEnum.Tether, eventCondition: ["Id:0011"], userControl: true)] + public void LawsofEarthFan(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Earth) return; + var sid = @event.SourceId(); + var sidx = accessory.GetPlayerIdIndex(sid); + HandleEarthPhaseTarget(sidx, accessory, $"扇形目标"); + + var dp = accessory.DrawFan(_bossId, 60f.DegToRad(), 0, 60f, 0, 4000, 4000, $"扇形引导"); + dp.TargetObject = sid; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Fan, dp); + + if (sid != accessory.Data.Me) return; + // 建立在分组正常的情况下,一左一右点名 + + // 左右浮空阵 + var targetPos1 = Enumerable.Repeat(new Vector3(0, 0, 0), 2).ToList(); + targetPos1[0] = GravityFieldLeft; + targetPos1[1] = GravityFieldLeft.FoldPointHorizon(CenterEarth.X); + + var targetPos2 = Enumerable.Repeat(new Vector3(0, 0, 0), 2).ToList(); + targetPos2[0] = new Vector3(90f, 0, 80.5f); + targetPos2[1] = targetPos2[0].FoldPointHorizon(CenterEarth.X); + + var myIndex = accessory.GetMyIndex(); + var idx = myIndex % 2 == 0 ? 0 : 1; + var bias = myIndex % 4 <= 1 ? new Vector3(0, 0, -5) : new Vector3(0, 0, 5); + + // 偷个懒,不从自己身上指路,做一个bias意为先踩塔后 + var dp1 = accessory.DrawGuidance(targetPos1[idx] + bias, targetPos1[idx], 0, 8000, + $"踩浮空阵{idx}"); + var dp2 = accessory.DrawGuidance(targetPos1[idx], targetPos2[idx], 0, 8000, $"放圈{idx}"); + dp2.Offset = new Vector3(0, -3.5f, 0); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + } + + private void HandleEarthPhaseTarget(int tidx, ScriptAccessory accessory, string targetType) + { + lock (_earthPhaseTarget) + { + _earthPhaseTarget[tidx] = true; + var count = _earthPhaseTarget.Count(x => x == true); + if (count == 4) _manualResetEvents[0].Set(); + + if (DebugMode) + { + accessory.DebugMsg($"检测到{targetType},现在有{count}个目标", DebugMode); + } + } + } + + [ScriptMethod(name: "第二轮踩塔提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(41003)$"], userControl: true)] + public void LawsofEarthGravityEmpireTower(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Earth) return; + + // var count = _earthPhaseTarget.Count(x => x == true); + // accessory.DebugMsg($"waitone前,现在有{count}个目标", DebugMode); + _manualResetEvents[0].WaitOne(); + // var count1 = _earthPhaseTarget.Count(x => x == true); + // accessory.DebugMsg($"waitone后,现在有{count1}个目标", DebugMode); + + var myIndex = accessory.GetMyIndex(); + List sortedTowerTargetIdxs; + + if (_intelligentPriorityValid) + { + var str = ""; + // 找到_earthPhaseTarget为false的四个index + var towerTargetIdxs = _earthPhaseTarget + .Select((value, index) => new { value, index }) + .Where(x => x.value == false) + .Select(x => x.index) + .ToList(); + str = $"要去踩塔的是:{accessory.BuildListStr(towerTargetIdxs)}\n"; + + // 找到_intelligentPriority中对应index的值 + var priorityValues = towerTargetIdxs + .Select(index => _intelligentPriority[index]) + .ToList(); + str = $"对应的优先级权重是:{accessory.BuildListStr(priorityValues)}\n"; + + // 将他们从小到大排序,并记下对应index + sortedTowerTargetIdxs = priorityValues + .Select((value, index) => new { OriginalIndex = towerTargetIdxs[index], Value = value }) + .OrderBy(x => x.Value) + .Select(x => x.OriginalIndex) + .ToList(); + str = $"踩塔智能优先级为:{accessory.BuildListStr(sortedTowerTargetIdxs)}\n"; + + accessory.DebugMsg($"{str}", DebugMode); + } + else + { + // 根据D1-MT-D3-H1, D2-ST-D4-H2的优先级排序 + List priorityIdx = [4, 0, 6, 2, 5, 1, 7, 3]; + + var towerTargetIdxs = _earthPhaseTarget + .Select((value, index) => new { value, index }) + .Where(x => x.value == false) + .Select(x => x.index) + .ToList(); + + var priorityMap = priorityIdx + .Select((value, index) => new { value, index }) + .ToDictionary(x => x.value, x => x.index); + + sortedTowerTargetIdxs = towerTargetIdxs + .OrderBy(index => priorityMap[index]) + .ToList(); + + var str = accessory.BuildListStr(sortedTowerTargetIdxs); + accessory.DebugMsg($"踩塔优先级为: {str}", DebugMode); + } + + if (_earthPhaseTarget[myIndex]) return; + + var myTowerIdx = sortedTowerTargetIdxs.IndexOf(myIndex); + List targetTowerPositions = Enumerable.Repeat(new Vector3(0, 0, 0), 4).ToList(); + targetTowerPositions[0] = new Vector3(GravityFieldLeft.X, 0, 89f); + targetTowerPositions[1] = targetTowerPositions[0].FoldPointVertical(GravityFieldLeft.Z); + targetTowerPositions[2] = targetTowerPositions[0].FoldPointHorizon(CenterEarth.X); + targetTowerPositions[3] = targetTowerPositions[1].FoldPointHorizon(CenterEarth.X); + + var dp = accessory.DrawGuidance(targetTowerPositions[myTowerIdx], 0, 8000, $"踩塔{myTowerIdx}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "土阶段重力帝国机制删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(41003)$"], userControl: false)] + public void LawsofEarthGravityEmpireRemove(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Earth) return; + accessory.Method.RemoveDraw($".*"); + _manualResetEvents[0].Reset(); + } + + #endregion 土阶段 + + #region 放陨石 + + [ScriptMethod(name: "放陨石", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4100[678])$"], userControl: true)] + public void MeteorImpactPos(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Earth) return; + var aid = @event.ActionId(); + var myIndex = accessory.GetMyIndex(); + var tid = @event.TargetId(); + + const uint meteorStart = 41006; + if (aid != meteorStart && tid != accessory.Data.Me) return; + + List targetPos = Enumerable.Range(0, 8) + .Select(_ => new Vector3[] { new(0, 0, 0), new(0, 0, 0) }) + .ToList(); + targetPos[0] = [new Vector3(88.5f, 0, GravityFieldLeft.Z), new Vector3(95.5f, 0, GravityFieldLeft.Z)]; + targetPos[1] = [new Vector3(104.5f, 0, GravityFieldLeft.Z), new Vector3(111.5f, 0, GravityFieldLeft.Z)]; + targetPos[4] = [new Vector3(88.5f, 0, 86.5f), new Vector3(95.5f, 0, 86.5f)]; + targetPos[5] = [new Vector3(104.5f, 0, 86.5f), new Vector3(111.5f, 0, 86.5f)]; + targetPos[2] = [CenterEarth, CenterEarth]; + targetPos[3] = [CenterEarth, CenterEarth]; + targetPos[6] = [CenterEarth, CenterEarth]; + targetPos[7] = [CenterEarth, CenterEarth]; + + if (aid == 41006) + { + var dp = accessory.DrawGuidance(targetPos[myIndex][0], 0, 8000, $"放陨石1"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + else if (!_drawn[0]) + { + _drawn[0] = true; + accessory.Method.RemoveDraw($"放陨石1"); + var dp = accessory.DrawGuidance(targetPos[myIndex][1], 0, 8000, $"放陨石2"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + else + { + accessory.Method.RemoveDraw(".*"); + } + } + + #endregion 放陨石 + + #region 绝对君权 + + [ScriptMethod(name: "---- 《绝对君权》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_AbsoluteAuthority(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "引导炮台", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(010[EF])$"], userControl: true)] + public void CoronationGuidance(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + if (sid != accessory.Data.Me) return; + var tpos = @event.TargetPosition(); + var dir = tpos.Position2Dirs(Center, 4); + var id = @event.Id(); + const uint relativeRightCorner = 0x010F; + + List targetPos = Enumerable.Repeat(new Vector3(0, 0, 0), 8).ToList(); + for (var i = 0; i < 4; i++) + { + targetPos[i] = Center.ExtendPoint((45f + 90f * i).DegToRad(), 27.5f); + targetPos[i + 4] = Center.ExtendPoint((90f * i).DegToRad(), 19.5f); + } + var myTargetPos = id == relativeRightCorner ? targetPos[dir] : targetPos[dir + 4]; + var dp = accessory.DrawGuidance(myTargetPos, 0, 10000, $"引导炮台"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "引导炮台消失", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(40982)$"], userControl: false)] + public void CoronationGuidanceRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + [ScriptMethod(name: "角落待命提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(41025)$"], userControl: true)] + public void AbsoluteAuthorityHint(Event @event, ScriptAccessory accessory) + { + var dp = accessory.DrawGuidance(new Vector3(119, 0, 81), 0, 10000, $"绝对君权右上待命"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "回中走位", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(41025)$"], userControl: true)] + public void AbsoluteAuthorityGuide(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"绝对君权右上待命"); + var dp1 = accessory.DrawGuidance(new Vector3(119, 0, 81), new Vector3(100, 0, 81), 0, 10000, $"绝对君权走位1"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp1); + var dp2 = accessory.DrawGuidance(new Vector3(100, 0, 81), Center, 0, 10000, $"绝对君权走位2"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp2); + } + + [ScriptMethod(name: "核爆走位", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(4186)$"], + userControl: true)] + public void AbsoluteAuthorityFlareGuide(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + if (tid != accessory.Data.Me) return; + _isFlareTarget = true; + var myIndex = accessory.GetMyIndex(); + + List flarePos = Enumerable.Repeat(new Vector3(0, 0, 0), 4).ToList(); + flarePos[0] = new(81, 0, 81); + flarePos[1] = flarePos[0].FoldPointHorizon(Center.X); + flarePos[2] = flarePos[0].FoldPointVertical(90); + flarePos[3] = flarePos[2].FoldPointHorizon(Center.X); + var myPosIdx = myIndex % 4; + + var dp = accessory.DrawGuidance(Center, flarePos[myPosIdx], 0, 10000, $"绝对君权核爆"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "绝对君权走位删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4103[01])$"], + userControl: false)] + public void AbsoluteAuthorityGuideRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($"绝对君权.*"); + } + + [ScriptMethod(name: "孤独感提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4103[01])$"], + userControl: true)] + public void AbsoluteAuthorityHeelGuidance(Event @event, ScriptAccessory accessory) + { + if (_drawn[1]) return; + _drawn[1] = true; + + if (!_isFlareTarget) return; + var myIndex = accessory.GetMyIndex(); + List partnerIdx = [2, 3, 0, 1, 6, 7, 4, 5]; + var myPartnerIdx = partnerIdx[myIndex]; + var dp = accessory.DrawConnectionBetweenTargets(accessory.Data.Me, accessory.Data.PartyList[myPartnerIdx], 0, + 10000, $"孤独感", 2f); + dp.Color = accessory.Data.DefaultSafeColor; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + accessory.Method.TextInfo($"与队友集合", 3000, true); + } + + [ScriptMethod(name: "孤独感提示删除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4103[23])$"], + userControl: false)] + public void AbsoluteAuthorityHeelGuideRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($".*"); + } + + #endregion 绝对君权 + + #region 冰阶段 + + [ScriptMethod(name: "---- 《冰阶段》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_LawsofIce(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "冰阶段转换", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:41013"], userControl: false)] + public void LawsofIcePhaseChange(Event @event, ScriptAccessory accessory) + { + if (_phase == QueenEternalPhase.Ice) return; + _phase = QueenEternalPhase.Ice; + accessory.DebugMsg($"当前阶段为:{_phase}", DebugMode); + accessory.Method.TextInfo($"保持移动!", 4000, true); + } + + [ScriptMethod(name: "行动提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:41014"], userControl: true)] + public void LawsofIcePhaseMoveHint(Event @event, ScriptAccessory accessory) + { + accessory.Method.TextInfo($"保持移动!", 4000, true); + } + + [ScriptMethod(name: "冰柱范围", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(0039|0001)$"], userControl: true)] + public void IceRushRange(Event @event, ScriptAccessory accessory) + { + var tid = @event.TargetId(); + var tidx = accessory.GetPlayerIdIndex(tid); + if (_iceRushRangeDrawn[tidx]) return; + + var sid = @event.SourceId(); + var myIndex = accessory.GetMyIndex(); + + lock (_earthPhaseTarget) + { + _iceRushRangeDrawn[tidx] = true; + var count = _earthPhaseTarget.Count(x => x == true); + var delay = count <= 4 ? 8000 : 4000; + var destroy = 4000; + var dp = accessory.DrawTarget2Target(sid, tid, 4, 80, delay, destroy, $"冰柱突刺范围{tidx}"); + dp.Color = tidx == myIndex ? accessory.Data.DefaultSafeColor : accessory.Data.DefaultDangerColor; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + } + + private List _upIceBridgeRoute = [new(100, 0, 96), new(90, 0, 96), new(110, 0, 96)]; + private List _downIceBridgeRoute = [new(100, 0, 104), new(90, 0, 104), new(110, 0, 104)]; + [ScriptMethod(name: "冰柱连线路径", eventType: EventTypeEnum.Tether, eventCondition: ["Id:regex:^(0039|0001)$"], userControl: true)] + public void IceRushRoute(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Ice) return; + var tid = @event.TargetId(); + var sid = @event.SourceId(); + if (tid != accessory.Data.Me) return; + if (_sourceIceDartPos != new Vector3(0, 0, 0)) return; + var spos = @event.SourcePosition(); + _sourceIceDartPos = spos; + + List targetPosList = Enumerable.Repeat(new Vector3(0, 0, 0), 4).ToList(); + + // 判断是否为第一轮 + var isFirstRound = _sourceIceDartPos.Z > 108; + // 91 97 103 109 ==-90==> 1 7 13 19 == /6 ==> 0 1 2 3 + if (isFirstRound) + { + var iceIdx = Math.Floor((_sourceIceDartPos.X - 90) / 6); + accessory.DebugMsg($"被第一轮下方冰柱,第{iceIdx+1}个所连", DebugMode); + List iceBaitPos = Enumerable.Repeat(new Vector3(0, 0, 0), 4).ToList(); + iceBaitPos[0] = new(108.5f, 0, 80.5f); + iceBaitPos[1] = new(115.5f, 0, 80.5f); + iceBaitPos[2] = iceBaitPos[1].FoldPointHorizon(CenterIce.X); + iceBaitPos[3] = iceBaitPos[0].FoldPointHorizon(CenterIce.X); + + targetPosList = iceIdx switch + { + 0 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[2], iceBaitPos[0]], + 1 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[2], iceBaitPos[1]], + 2 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[1], iceBaitPos[2]], + _ => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[1], iceBaitPos[3]], + }; + + _myIceBridgeIdx = (int)iceIdx; + } + else + { + var iceIdx = _sourceIceDartPos.Position2Dirs(CenterIce, 4, false); + accessory.DebugMsg($"被第二轮左右冰柱,北顺时针起第{iceIdx+1}个所连", DebugMode); + List iceBaitPos = Enumerable.Repeat(new Vector3(0, 0, 0), 4).ToList(); + iceBaitPos[0] = new(84.5f, 0, 109.5f); + iceBaitPos[1] = iceBaitPos[0].FoldPointVertical(CenterIce.Z); + iceBaitPos[2] = iceBaitPos[1].FoldPointHorizon(CenterIce.X); + iceBaitPos[3] = iceBaitPos[0].FoldPointHorizon(CenterIce.X); + + targetPosList = iceIdx switch + { + 0 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[1], iceBaitPos[0]], + 1 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[1], iceBaitPos[1]], + 2 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[2], iceBaitPos[2]], + _ => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[2], iceBaitPos[3]], + }; + + var bridgeStr = iceIdx switch + { + 0 => "左下", + 1 => "左上", + 2 => "右上", + _ => "右下", + }; + + _myIceBridgeIdx = 10 + iceIdx; + accessory.Method.TextInfo($"观察【{bridgeStr}】桥梁,等第一轮先过", 4000, true); + } + + var dp01 = accessory.DrawGuidance(targetPosList[0], targetPosList[1], 0, 15000, $"冰柱路径01"); + var dp12 = accessory.DrawGuidance(targetPosList[1], targetPosList[2], 0, 15000, $"冰柱路径12"); + var dp23 = accessory.DrawGuidance(targetPosList[2], targetPosList[3], 0, 15000, $"冰柱路径23"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp01); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp12); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp23); + } + + [ScriptMethod(name: "回中躲左右刀路径", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4101[56])"], userControl: true)] + public void AfterIceRushRoute(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Ice) return; + if (_drawn[2]) return; + _drawn[2] = true; + accessory.Method.RemoveDraw($"冰柱路径.*"); + accessory.Method.RemoveDraw($"冰柱突刺范围.*"); + + List targetPosList = _myIceBridgeIdx switch + { + 0 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[2]], + 1 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[2]], + 2 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[1]], + 3 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[1]], + + 10 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[1]], + 11 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[1]], + 12 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[2]], + 13 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[2]], + }; + + var dp21 = accessory.DrawGuidance(targetPosList[2], targetPosList[1], 0, 15000, $"回中路径01"); + var dp10 = accessory.DrawGuidance(targetPosList[1], targetPosList[0], 0, 15000, $"回中路径12"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp21); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp10); + + if (_myIceBridgeIdx >= 10) + accessory.Method.TextInfo($"【先】过桥回中", 4000, false); + else + accessory.Method.TextInfo($"【后】过桥回中", 4000, true); + } + + [ScriptMethod(name: "回中路径移除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4099[02])"], userControl: false)] + public void AfterIceRushRouteRemove(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Ice) return; + accessory.Method.RemoveDraw($"回中路径.*"); + } + + private bool _raisedTribute; + [ScriptMethod(name: "接线分摊", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(020C)"], userControl: true)] + public void RaisedTributeRoute(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Ice) return; + if (_raisedTributeNum != 0) return; + + _raisedTribute = true; + + var myIndex = accessory.GetMyIndex(); + if (myIndex != 4 && myIndex != 5) return; + var topRightCorner = new Vector3(115.5f, 0, 80.5f); + var topLeftCorner = topRightCorner.FoldPointHorizon(CenterIce.X); + List targetPosList = myIndex switch + { + 4 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[1], topLeftCorner], + 5 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[2], topRightCorner], + }; + + var dp01 = accessory.DrawGuidance(targetPosList[0], targetPosList[1], 0, 15000, $"D接线路径01"); + var dp12 = accessory.DrawGuidance(targetPosList[1], targetPosList[2], 0, 15000, $"D接线路径12"); + var dp23 = accessory.DrawGuidance(targetPosList[2], targetPosList[3], 0, 15000, $"D接线路径23"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp01); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp12); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp23); + } + + [ScriptMethod(name: "接线分摊增加", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(41024)", "TargetIndex:1"], userControl: false)] + public void RaisedTributeNumPlus(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Ice) return; + _raisedTributeNum++; + } + + [ScriptMethod(name: "接线分摊第二轮", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(020C)"], userControl: false)] + public void RaisedTributeRouteSecond(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Ice) return; + if (_raisedTributeNum != 1) return; + var myIndex = accessory.GetMyIndex(); + + _raisedTribute = false; + accessory.Method.RemoveDraw($"D接线.*"); + + var topRightCorner = new Vector3(115.5f, 0, 80.5f); + var topLeftCorner = topRightCorner.FoldPointHorizon(CenterIce.X); + + List idxList = [0, 1, 4, 5]; + if (!idxList.Contains(myIndex)) return; + + List targetPosList = myIndex switch + { + 0 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[1], topLeftCorner], + 1 => [CenterIce, _upIceBridgeRoute[0], _upIceBridgeRoute[2], topRightCorner], + + 4 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[1], topLeftCorner], + 5 => [CenterIce, _downIceBridgeRoute[0], _downIceBridgeRoute[2], topRightCorner], + }; + + if (myIndex <= 1) + { + var dp01 = accessory.DrawGuidance(targetPosList[0], targetPosList[1], 0, 15000, $"T接线路径01"); + var dp12 = accessory.DrawGuidance(targetPosList[1], targetPosList[2], 0, 15000, $"T接线路径12"); + var dp23 = accessory.DrawGuidance(targetPosList[2], targetPosList[3], 0, 15000, $"T接线路径23"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp01); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp12); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp23); + } + else + { + var dp32 = accessory.DrawGuidance(targetPosList[3], targetPosList[2], 0, 15000, $"D返回路径32"); + var dp21 = accessory.DrawGuidance(targetPosList[2], targetPosList[1], 0, 15000, $"D返回路径21"); + var dp10 = accessory.DrawGuidance(targetPosList[1], targetPosList[0], 0, 15000, $"D返回路径10"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp32); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp21); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp10); + } + } + + [ScriptMethod(name: "接线分摊移除", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(020C)"], userControl: false)] + public void RaisedTributeRouteRemove(Event @event, ScriptAccessory accessory) + { + if (_phase != QueenEternalPhase.Ice) return; + if (_raisedTributeNum <= 2) return; + accessory.Method.RemoveDraw($".*"); + } + + #endregion 冰阶段 + + #region 激进切换 + + [ScriptMethod(name: "---- 《软狂暴》 ----", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld"], + userControl: true)] + public void SplitLine_Enrage(Event @event, ScriptAccessory accessory) + { + } + + [ScriptMethod(name: "固定安全点", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(41039)"], userControl: true)] + public void RadicalShift(Event @event, ScriptAccessory accessory) + { + if (_phase == QueenEternalPhase.Ice) + _phase = QueenEternalPhase.Enrage; + var myIndex = accessory.GetMyIndex(); + List safePos = Enumerable.Repeat(new Vector3(0, 0, 0), 8).ToList(); + safePos[0] = new Vector3(91.3f, 0f, 86.6f); + safePos[1] = safePos[0].FoldPointHorizon(Center.X); + safePos[2] = new Vector3(95.5f, 0f, 94.6f); + safePos[3] = safePos[2].FoldPointHorizon(Center.X); + safePos[4] = new Vector3(91.45f, 0f, 98f); + safePos[5] = safePos[4].FoldPointHorizon(Center.X); + safePos[6] = new Vector3(88.35f, 0f, 101.65f); + safePos[7] = safePos[6].FoldPointHorizon(Center.X); + + var dp = accessory.DrawGuidance(safePos[myIndex], 0, 15000, $"固定安全点{myIndex}"); + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "固定安全点移除", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(41039)"], userControl: false)] + public void RadicalShiftRemove(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw(".*"); + } + + private bool _exaflareShown = false; + [ScriptMethod(name: "地火范围", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(41043)"], userControl: true)] + public void Exaflare(Event @event, ScriptAccessory accessory) + { + _exaflareShown = true; + var sid = @event.SourceId(); + var dp = accessory.DrawCircle(sid, 6, 0, 5000, $"地火{sid}", true); + dp.Color = exflareColor.V4.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dpWarn = accessory.DrawCircle(sid, 6, 0, 6100, $"地火警告{sid}"); + dpWarn.Offset = new Vector3(0, 0, -8.5f); + dpWarn.Color = exflareWarnColor.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dpWarn); + } + + + [ScriptMethod(name: "地火范围后续", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(4104[34])"], userControl: false)] + public void ExaflareRest(Event @event, ScriptAccessory accessory) + { + var sid = @event.SourceId(); + var spos = @event.SourcePosition(); + if (!_exaflareShown) return; + + var dp = accessory.DrawCircle(sid, 6, 0, 1100, $"地火警告{sid}"); + dp.Offset = new Vector3(0, 0, -8.5f); + dp.Color = exflareColor.V4.WithW(2f); + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + + var dpWarn = accessory.DrawCircle(sid, 6, 0, 2200, $"地火警告{sid}"); + dpWarn.Offset = new Vector3(0, 0, -17f); + dpWarn.Color = exflareWarnColor.V4; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dpWarn); + } + + #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 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 TargetId(this Event @event) + { + return ParseHexId(@event["TargetId"], out var id) ? id : 0; + } + + public static uint TargetIndex(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetIndex"]); + } + + 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 float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint Index(this Event @event) + { + return ParseHexId(@event["Index"], out var id) ? id : 0; + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var id) ? id : 0; + } + + public static uint DirectorId(this Event @event) + { + return ParseHexId(@event["DirectorId"], out var id) ? id : 0; + } + public static uint Id(this Event @event) + { + return ParseHexId(@event["Id"], out var id) ? id : 0; + } + + public static uint DataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DataId"]); + } + + 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 class DirectionCalc +{ + 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建立list + // Game List Logic + // 0 - 4 pi + // 0.25 pi - 3 0.75pi + // 0.5 pi - 2 0.5pi + // 0.75 pi - 1 0.25pi + // pi - 0 0 + // 1.25 pi - 7 1.75pi + // 1.5 pi - 6 1.5pi + // 1.75 pi - 5 1.25pi + // Logic = Pi - Game (+ 2pi) + + /// + /// 将游戏基角度(以南为0,逆时针增加)转为逻辑基角度(以北为0,顺时针增加) + /// 算法与Logic2Game完全相同,但为了代码可读性,便于区分。 + /// + /// 游戏基角度 + /// 逻辑基角度 + public static float Game2Logic(this float radian) + { + // if (r < 0) r = (float)(r + 2 * Math.PI); + // if (r > 2 * Math.PI) r = (float)(r - 2 * Math.PI); + + var r = float.Pi - radian; + r = (r + float.Pi * 2) % (float.Pi * 2); + return r; + } + + /// + /// 将逻辑基角度(以北为0,顺时针增加)转为游戏基角度(以南为0,逆时针增加) + /// 算法与Game2Logic完全相同,但为了代码可读性,便于区分。 + /// + /// 逻辑基角度 + /// 游戏基角度 + public static float Logic2Game(this float radian) + { + // var r = (float)Math.PI - radian; + // if (r < Math.PI) r = (float)(r + 2 * Math.PI); + // if (r > Math.PI) r = (float)(r - 2 * Math.PI); + + return radian.Game2Logic(); + } + + /// + /// 输入逻辑基角度,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 逻辑基角度 + /// 方位总数 + /// 斜分割,默认true + /// 逻辑基角度对应的逻辑方位 + public static int Rad2Dirs(this float radian, int dirs, bool diagDivision = true) + { + var r = diagDivision + ? Math.Round(radian / (2f * float.Pi / dirs)) + : Math.Floor(radian / (2f * float.Pi / dirs)); + r = (r + dirs) % dirs; + return (int)r; + } + + /// + /// 输入坐标,获取逻辑方位(斜分割以正上为0,正分割以右上为0,顺时针增加) + /// + /// 坐标点 + /// 中心点 + /// 方位总数 + /// 斜分割,默认true + /// 该坐标点对应的逻辑方位 + public static int Position2Dirs(this Vector3 point, Vector3 center, int dirs, bool diagDivision = true) + { + double dirsDouble = dirs; + var r = diagDivision + ? Math.Round(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble + : Math.Floor(dirsDouble / 2 - dirsDouble / 2 * Math.Atan2(point.X - center.X, point.Z - center.Z) / Math.PI) % dirsDouble; + return (int)r; + } + + /// + /// 以逻辑基弧度旋转某点 + /// + /// 待旋转点坐标 + /// 中心 + /// 旋转弧度 + /// 旋转后坐标点 + public static Vector3 RotatePoint(this Vector3 point, Vector3 center, float radian) + { + // 围绕某点顺时针旋转某弧度 + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var rot = MathF.PI - MathF.Atan2(v2.X, v2.Y) + radian; + var length = v2.Length(); + return new Vector3(center.X + MathF.Sin(rot) * length, center.Y, center.Z - MathF.Cos(rot) * length); + } + + /// + /// 以逻辑基角度从某中心点向外延伸 + /// + /// 待延伸中心点 + /// 旋转弧度 + /// 延伸长度 + /// 延伸后坐标点 + public static Vector3 ExtendPoint(this Vector3 center, float radian, float length) + { + // 令某点以某弧度延伸一定长度 + return new Vector3(center.X + MathF.Sin(radian) * length, center.Y, center.Z - MathF.Cos(radian) * length); + } + + /// + /// 寻找外侧某点到中心的逻辑基弧度 + /// + /// 中心 + /// 外侧点 + /// 外侧点到中心的逻辑基弧度 + public static float FindRadian(this Vector3 newPoint, Vector3 center) + { + var radian = MathF.PI - MathF.Atan2(newPoint.X - center.X, newPoint.Z - center.Z); + if (radian < 0) + radian += 2 * MathF.PI; + return radian; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointHorizon(this Vector3 point, float centerX) + { + return point with { X = 2 * centerX - point.X }; + } + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointVertical(this Vector3 point, float centerZ) + { + return point with { Z = 2 * centerZ - point.Z }; + } + + /// + /// 将输入点朝某中心点往内/外同角度延伸,默认向内 + /// + /// 待延伸点 + /// 中心点 + /// 延伸长度 + /// 是否向外延伸> + /// + public static Vector3 PointInOutside(this Vector3 point, Vector3 center, float length, bool isOutside = false) + { + Vector2 v2 = new(point.X - center.X, point.Z - center.Z); + var targetPos = (point - center) / v2.Length() * length * (isOutside ? 1 : -1) + point; + return targetPos; + } + + /// + /// 获得两点之间距离 + /// + /// + /// + /// + public static float DistanceTo(this Vector3 point, Vector3 target) + { + Vector2 v2 = new(point.X - target.X, point.Z - target.Z); + return v2.Length(); + } +} + +public static class IndexHelper +{ + /// + /// 输入玩家dataId,获得对应的位置index + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置index + public static int GetPlayerIdIndex(this ScriptAccessory accessory, uint pid) + { + // 获得玩家 IDX + return accessory.Data.PartyList.IndexOf(pid); + } + + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory accessory) + { + return accessory.Data.PartyList.IndexOf(accessory.Data.Me); + } + + /// + /// 输入玩家dataId,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 玩家SourceId + /// + /// 该玩家对应的位置称呼 + public static string GetPlayerJobById(this ScriptAccessory accessory, uint pid) + { + // 获得玩家职能简称,无用处,仅作DEBUG输出 + var idx = accessory.Data.PartyList.IndexOf(pid); + var str = accessory.GetPlayerJobByIndex(idx); + return str; + } + + /// + /// 输入位置index,获得对应的位置称呼,输出字符仅作文字输出用 + /// + /// 位置index + /// 是否为四人迷宫 + /// + /// + public static string GetPlayerJobByIndex(this ScriptAccessory accessory, int idx, bool fourPeople = false) + { + var str = idx switch + { + 0 => "MT", + 1 => fourPeople ? "H1" : "ST", + 2 => fourPeople ? "D1" : "H1", + 3 => fourPeople ? "D2" : "H2", + 4 => "D1", + 5 => "D2", + 6 => "D3", + 7 => "D4", + _ => "unknown" + }; + return str; + } +} + +public static class ColorHelper +{ + public static ScriptColor ColorRed = new ScriptColor { V4 = new Vector4(1.0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorPink = new ScriptColor { V4 = new Vector4(1f, 0f, 1f, 1.0f) }; + public static ScriptColor ColorCyan = new ScriptColor { V4 = new Vector4(0f, 1f, 0.8f, 1.0f) }; + public static ScriptColor ColorDark = new ScriptColor { V4 = new Vector4(0f, 0f, 0f, 1.0f) }; + public static ScriptColor ColorLightBlue = new ScriptColor { V4 = new Vector4(0.48f, 0.40f, 0.93f, 1.0f) }; + public static ScriptColor ColorWhite = new ScriptColor { V4 = new Vector4(1f, 1f, 1f, 2f) }; + public static ScriptColor ColorYellow = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + +} + +public static class AssignDp +{ + /// + /// 返回箭头指引相关dp + /// + /// + /// 箭头起始,可输入uint或Vector3 + /// 箭头指向目标,可输入uint或Vector3,为0则无目标 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 箭头旋转角度 + /// 箭头宽度 + /// + /// + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Rotation = rotation; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = accessory.Data.DefaultSafeColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + + switch (ownerObj) + { + case uint sid: + dp.Owner = sid; + break; + case Vector3 spos: + dp.Position = spos; + break; + default: + throw new ArgumentException("ownerObj的目标类型输入错误"); + } + + switch (targetObj) + { + case uint tid: + if (tid != 0) dp.TargetObject = tid; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + } + + return dp; + } + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory accessory, + object targetObj, int delay, int destroy, string name, float rotation = 0, float scale = 1f) + { + return targetObj switch + { + uint uintTarget => accessory.DrawGuidance(accessory.Data.Me, uintTarget, delay, destroy, name, rotation, scale), + Vector3 vectorTarget => accessory.DrawGuidance(accessory.Data.Me, vectorTarget, delay, destroy, name, rotation, scale), + _ => throw new ArgumentException("targetObj 的类型必须是 uint 或 Vector3") + }; + } + + /// + /// 返回扇形左右刀 + /// + /// + /// 起始目标id,通常为boss + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 是左刀 + /// 扇形角度 + /// 扇形尺寸 + /// 动画效果随时间填充 + /// + public static DrawPropertiesEdit DrawLeftRightCleave(this ScriptAccessory accessory, uint ownerId, bool isLeftCleave, int delay, int destroy, string name, float radian = float.Pi, float scale = 60f, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Radian = radian; + dp.Rotation = isLeftCleave ? float.Pi / 2 : -float.Pi / 2; + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回扇形前后刀 + /// + /// + /// 起始目标id,通常为boss + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 是前刀 + /// 扇形角度 + /// 扇形尺寸 + /// 动画效果随时间填充 + /// + public static DrawPropertiesEdit DrawFrontBackCleave(this ScriptAccessory accessory, uint ownerId, bool isFrontCleave, int delay, int destroy, string name, float radian = float.Pi, float scale = 60f, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Radian = radian; + dp.Rotation = isFrontCleave ? 0 : -float.Pi; + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回距离某对象目标最近/最远的dp + /// + /// + /// 起始目标id,通常为boss + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图宽度 + /// 绘图长度 + /// true为最近,false为最远 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// + public static DrawPropertiesEdit DrawTargetNearFarOrder(this ScriptAccessory accessory, uint ownerId, uint orderIdx, + bool isNear, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = + isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回距离某坐标位置最近/最远的dp + /// + /// + /// 特定坐标点 + /// 顺序,从1开始 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 绘图宽度 + /// 绘图长度 + /// true为最近,false为最远 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// + public static DrawPropertiesEdit DrawPositionNearFarOrder(this ScriptAccessory accessory, Vector3 position, uint orderIdx, + bool isNear, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Position = position; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.TargetResolvePattern = + isNear ? PositionResolvePatternEnum.PlayerNearestOrder : PositionResolvePatternEnum.PlayerFarestOrder; + dp.TargetOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回ownerId施法目标的dp + /// + /// + /// 起始目标id,通常为boss + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawOwnersTarget(this ScriptAccessory accessory, uint ownerId, float width, float length, int delay, + int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.TargetResolvePattern = PositionResolvePatternEnum.OwnerTarget; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回ownerId仇恨相关的dp + /// + /// + /// 起始目标id,通常为boss + /// 仇恨顺序,从1开始 + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 动画效果随时间填充 + /// 长度是否随距离改变 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawOwnersEntityOrder(this ScriptAccessory accessory, uint ownerId, uint orderIdx, float width, float length, int delay, int destroy, string name, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.CentreResolvePattern = PositionResolvePatternEnum.OwnerEnmityOrder; + dp.CentreOrderIndex = orderIdx; + dp.Color = accessory.Data.DefaultDangerColor; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回owner与target的dp,可修改 dp.Owner, dp.TargetObject, dp.Scale + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 绘图旋转角度 + /// 绘图宽度 + /// 绘图长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 长度是否随距离改变 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawTarget2Target(this ScriptAccessory accessory, uint ownerId, uint targetId, float width, float length, int delay, int destroy, string name, float rotation = 0, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Rotation = rotation; + dp.Owner = ownerId; + dp.TargetObject = targetId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= lengthByDistance ? ScaleMode.YByDistance : ScaleMode.None; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + public static DrawPropertiesEdit DrawFanToTarget(this ScriptAccessory accessory, uint sourceId, uint targetId, float radian, float scale, int delay, int destroy, string name, Vector4 color, float rotation = 0, bool lengthByDistance = false, bool byTime = false) + { + var dp = accessory.DrawTarget2Target(sourceId, targetId, scale, scale, delay, destroy, name, rotation, lengthByDistance, byTime); + dp.Radian = radian; + dp.Color = color; + return dp; + } + + /// + /// 返回owner与target之间的连线dp,使用Line绘制 + /// + /// + /// 起始目标id,通常为自己 + /// 目标单位id + /// 线条宽度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawConnectionBetweenTargets(this ScriptAccessory accessory, uint ownerId, + uint targetId, int delay, int destroy, string name, float scale = 1f) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Owner = ownerId; + dp.TargetObject = targetId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= ScaleMode.YByDistance; + return dp; + } + + /// + /// 返回圆形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 圆圈尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory accessory, uint ownerId, float scale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回环形dp,跟随owner,可修改 dp.Owner, dp.Scale + /// + /// 起始目标id,通常为自己或Boss + /// 延时delay ms出现 + /// 外环实心尺寸 + /// 内环空心尺寸 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory accessory, uint ownerId, float scale, float innerScale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.DrawFan(ownerId, float.Pi * 2, 0, scale, innerScale, delay, destroy, name, byTime); + return dp; + } + + /// + /// 返回静态dp,通常用于指引固定位置。可修改 dp.Position, dp.Rotation, dp.Scale + /// + /// 绘图起始,可输入uint或Vector3 + /// 绘图目标,可输入uint或Vector3,为0则无目标 + /// 图形角度 + /// 旋转角度,以北为0度顺时针 + /// 绘图宽度 + /// 绘图长度 + /// 是Vector4则选用该颜色 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + /// + public static DrawPropertiesEdit DrawStatic(this ScriptAccessory accessory, object ownerObj, object targetObj, + float radian, float rotation, float width, float length, object color, int delay, int destroy, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + switch (ownerObj) + { + case uint sid: + dp.Owner = sid; + break; + case Vector3 spos: + dp.Position = spos; + break; + } + switch (targetObj) + { + case uint tid: + if (tid != 0) dp.TargetObject = tid; + break; + case Vector3 tpos: + dp.TargetPosition = tpos; + break; + } + dp.Radian = radian; + dp.Rotation = rotation.Logic2Game(); + switch (color) + { + case Vector4 clr: + dp.Color = clr; + break; + default: + dp.Color = accessory.Data.DefaultDangerColor; + break; + } + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + /// + /// 返回静态圆圈dp,通常用于指引固定位置。 + /// + /// + /// 圆圈中心位置 + /// 圆圈颜色 + /// 圆圈尺寸,默认1.5f + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawStaticCircle(this ScriptAccessory accessory, Vector3 center, Vector4 color, int delay, int destroy, string name, float scale = 1.5f) + { + var dp = accessory.DrawStatic(center, (uint)0, 0, 0, scale, scale, color, delay, destroy, name); + return dp; + } + + /// + /// 返回静态月环dp,通常用于指引固定位置。 + /// + /// + /// 月环中心位置 + /// 月环颜色 + /// 月环外径,默认1.5f + /// 月环内径,默认scale-0.05f + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// + public static DrawPropertiesEdit DrawStaticDonut(this ScriptAccessory accessory, Vector3 center, Vector4 color, int delay, int destroy, string name, float scale, float innerscale = 0) + { + var dp = accessory.DrawStatic(center, (uint)0, float.Pi * 2, 0, scale, scale, color, delay, destroy, name); + dp.InnerScale = innerscale != 0f ? new Vector2(innerscale) : new Vector2(scale - 0.05f); + return dp; + } + + /// + /// 返回矩形 + /// + /// 起始目标id,通常为自己或Boss + /// 矩形宽度 + /// 矩形长度 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawRect(this ScriptAccessory accessory, uint ownerId, float width, float length, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回扇形 + /// + /// 起始目标id,通常为自己或Boss + /// 扇形弧度 + /// 图形旋转角度 + /// 扇形尺寸 + /// 扇形内环空心尺寸 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawFan(this ScriptAccessory accessory, uint ownerId, float radian, float rotation, float scale, float innerScale, int delay, int destroy, string name, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(scale); + dp.InnerScale = new Vector2(innerScale); + dp.Radian = radian; + dp.Rotation = rotation; + dp.Owner = ownerId; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + /// + /// 返回击退 + /// + /// + /// 击退源,可输入uint或Vector3 + /// 击退绘图宽度 + /// 击退绘图长度/距离 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 起始目标ID,通常为自己或其他玩家 + /// 动画效果随时间填充 + /// + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory accessory, uint ownerId, object target, float length, int delay, int destroy, string name, float width = 1.5f, bool byTime = false) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Scale = new Vector2(width, length); + dp.Owner = ownerId; + switch (target) + { + // 根据传入的 tid 类型来决定是使用 TargetObject 还是 TargetPosition + case uint tid: + dp.TargetObject = tid; // 如果 tid 是 uint 类型 + break; + case Vector3 tpos: + dp.TargetPosition = tpos; // 如果 tid 是 Vector3 类型 + break; + default: + throw new ArgumentException("DrawKnockBack的目标类型输入错误"); + } + dp.Rotation = float.Pi; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Delay = delay; + dp.DestoryAt = destroy; + dp.ScaleMode |= byTime ? ScaleMode.ByTime : ScaleMode.None; + return dp; + } + + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory accessory, object target, float length, int delay, int destroy, string name, float width = 1.5f, bool byTime = false) + { + return target switch + { + uint uintTarget => accessory.DrawKnockBack(accessory.Data.Me, uintTarget, length, delay, destroy, name, width, byTime), + Vector3 vectorTarget => accessory.DrawKnockBack(accessory.Data.Me, vectorTarget, length, delay, destroy, name, width, byTime), + _ => throw new ArgumentException("target 的类型必须是 uint 或 Vector3") + }; + } + + /// + /// 返回背对 + /// + /// + /// 背对源,可输入uint或Vector3 + /// 延时delay ms出现 + /// 绘图自出现起,经destroy ms消失 + /// 绘图名称 + /// 起始目标ID,通常为自己或其他玩家 + /// + /// + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory accessory, uint ownerId, object target, int delay, int destroy, string name) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = name; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = ownerId; + switch (target) + { + // 根据传入的 tid 类型来决定是使用 TargetObject 还是 TargetPosition + case uint tid: + dp.TargetObject = tid; // 如果 tid 是 uint 类型 + break; + case Vector3 tpos: + dp.TargetPosition = tpos; // 如果 tid 是 Vector3 类型 + break; + default: + throw new ArgumentException("DrawSightAvoid的目标类型输入错误"); + } + dp.Delay = delay; + dp.DestoryAt = destroy; + return dp; + } + + public static DrawPropertiesEdit DrawSightAvoid(this ScriptAccessory accessory, object target, int delay, int destroy, string name) + { + return target switch + { + uint uintTarget => accessory.DrawSightAvoid(accessory.Data.Me, uintTarget, delay, destroy, name), + Vector3 vectorTarget => accessory.DrawSightAvoid(accessory.Data.Me, vectorTarget, delay, destroy, name), + _ => throw new ArgumentException("target 的类型必须是 uint 或 Vector3") + }; + } + + /// + /// 返回多方向延伸指引 + /// + /// + /// 分散源 + /// 分散角度 + /// 玩家对应角度idx + /// 指引箭头宽度 + /// 指引箭头长度 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 玩家对应箭头指引颜色 + /// 其他玩家对应箭头指引颜色 + /// + /// + public static List DrawExtendDirection(this ScriptAccessory accessory, object owner, + List extendDirs, int myDirIdx, float width, float length, int delay, int destroy, string name, + Vector4 colorPlayer, Vector4 colorNormal) + { + List dpList = []; + switch (owner) + { + case uint sid: + for (var i = 0; i < extendDirs.Count; i++) + { + var dp = accessory.DrawRect(sid, width, length, delay, destroy, $"{name}{i}"); + dp.Rotation = extendDirs[i]; + dp.Color = i == myDirIdx ? colorPlayer : colorNormal; + dpList.Add(dp); + } + break; + case Vector3 spos: + for (var i = 0; i < extendDirs.Count; i++) + { + var dp = accessory.DrawGuidance(spos, spos.ExtendPoint(extendDirs[i], length), delay, destroy, + $"{name}{i}", 0, width); + dp.Color = i == myDirIdx ? colorPlayer : colorNormal; + dpList.Add(dp); + } + break; + default: + throw new ArgumentException("DrawExtendDirection的目标类型输入错误"); + } + + return dpList; + } + + /// + /// 返回多地点指路指引列表 + /// + /// + /// 地点位置 + /// 绘图出现延时 + /// 绘图消失时间 + /// 绘图名称 + /// 对应位置标记行动颜色 + /// 对应位置标记准备颜色 + /// 指路出发箭头颜色 + /// 指路准备箭头颜色 + /// dpList中的三个List:位置标记,玩家指路箭头,地点至下个地点的指路箭头 + public static List> DrawMultiGuidance(this ScriptAccessory accessory, + List positions, List delay, List destroy, string name, + Vector4 colorGo, Vector4 colorPrepare, Vector4 colorPosNormal, Vector4 colorPosPlayer) + { + List> dpList = [[], [], []]; + for (var i = 0; i < positions.Count; i++) + { + var dpPos = accessory.DrawStaticCircle(positions[i], colorPosPlayer, delay[i], destroy[i], $"{name}pos{i}"); + dpList[0].Add(dpPos); + var dpGuide = accessory.DrawGuidance(positions[i], colorGo, delay[i], destroy[i], $"{name}guide{i}"); + dpList[1].Add(dpGuide); + if (i == positions.Count - 1) break; + var dpPrep = accessory.DrawGuidance(positions[i], positions[i + 1], delay[i], destroy[i], $"{name}prep{i}"); + dpList[2].Add(dpPrep); + } + return dpList; + } + + /// + /// 外部用调试模式 + /// + /// + /// + /// + public static void DebugMsg(this ScriptAccessory accessory, string str, bool debugMode = true) + { + if (!debugMode) + return; + accessory.Method.SendChat($"/e [DEBUG] {str}"); + } + + public static string BuildListStr(this ScriptAccessory accessory, List myList) + { + return string.Join(", ", myList.Select(item => item?.ToString() ?? "")); + } +} + +public enum QueenEternalPhase : uint +{ + Init, + Wind, + Earth, + Ice, + Enrage +} + +#endregion diff --git a/lianying1997/07-Dawntrail/UDM/UDM_P3.cs b/lianying1997/07-Dawntrail/UDM/UDM_P3.cs new file mode 100644 index 0000000..34bd69c --- /dev/null +++ b/lianying1997/07-Dawntrail/UDM/UDM_P3.cs @@ -0,0 +1,3772 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameOperate; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using System.Globalization; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; + +namespace UsamisKodakku.Scripts._07_DawnTrail.UDM_P3; + +[ScriptType(name: Name, territorys: [1363], guid: "c902dd56-629e-4033-a77f-fb23b8d94745", + version: Version, author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +public class UDM_P3 +{ + const string NoteStr = + $""" + {Version} + 二运黑洞 + - 含指路与指挥模式,需在用户设置开启,副本内使用 `/e kdyhd` 可测试标点优先级。 + - * 黑洞指路依赖标点,黑洞出现前需完成整个标点流程。 + - 共有五种优先级配置,其中: + - * HDT("43765812"):采用 H2 > H1 > D3 > D2 > D1 > D4 > MT > ST + - * HDT三麻取反("437658120"):基于 HDT,三麻优先级取反(ST > MT > ... > H2) + - * THD("12345678"):采用 MT > ST > H1 > H2 > D1 > D2 > D3 > D4 + - * 若选择自定义,可从左到右细分各职能标点顺序,支持8位或9位输入。 + - * 前八位由"1~8"组成,第九位输入"0"代表三麻取反,输入错误则回退到“HDT三麻取反”。 + + 二运黑洞指挥策略 - 美式T2泥3 + - 根据职能与 Buff 固定标点,其本质为 DTH,但固定T摇2。 + - 无Buff D:攻/锁/禁1;无Buff T/H:攻/锁/禁2;泥土 H/D:攻/锁3 + - 可避免T摇攻1禁1满地乱爬、摇攻3卡奥斯离开场中读半场刀、摇锁3白洞前清血的毒点。 + + 特殊方法 + - 屏蔽艾克斯迪司释放钢铁暴雷时的连线 + - 巨大凯夫卡释放上桌技能时透明化 + - 治疗与输出职业在决战 Buff 期间,无法选中不能造成伤害的 Boss + """; + + const string UpdateInfo = + $""" + {Version} + 1. 增加黑洞指示线颜色控制。 + """; + + private const string Name = "绝妖星乱舞_P3"; + private const string Version = "0.0.0.41"; + private const string DebugVersion = "a"; + private int _runId = 0; + + public 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); + private static readonly Vector3 CrystalBasePos = new Vector3(109.9f, 0, 109.9f); + private static readonly Vector3 BlackHoleBasePos = new Vector3(100f, 0, 83f); + private UDMP3Params _udmP3Param = new UDMP3Params(); + private PriorityDict _pd = new PriorityDict(); + private PriorityDict _pdCaptain = new PriorityDict(); + + [UserSetting("启用方法设置中带*的特殊功能")] + public static bool SpecialMode { get; set; } = false; + + [UserSetting("P3 - 卡奥斯诅咒敕令绘图颜色")] + public ScriptColor CursedEdictColor { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 0.643f, 0f, 1.0f) }; + + [UserSetting("P3 - 卡奥斯诅咒敕令绘图深度(Imgui请调2以内)")] + public static float CursedEdictWhiteVal { get; set; } = 4; + + [UserSetting("P3A1 - 一运深层痛楚策略")] + public static BoAStgEnum BoAStg { get; set; } = BoAStgEnum.TLB_纯固定_NoCCHH; + public enum BoAStgEnum + { + // TLB_火炸人群_OverClock, + TLB_拉火水晶_夜音, + TLB_纯固定_NoCCHH, + TLB_相反水晶_盗火文档, + 正攻, + } + + [UserSetting("P3B1 - 二运黑洞指挥模式")] + public static bool P3B1CaptainMode { get; set; } = false; + + [UserSetting("P3B1 - 帮助调试的指挥模式聊天框输出")] + public static bool P3B1CaptainModeDevHelper { get; set; } = true; + + [UserSetting("P3B1 - 二运黑洞指示线颜色")] + public ScriptColor BhConnLineColor { get; set; } = new ScriptColor { V4 = new Vector4(1.0f, 1.0f, 0f, 1.0f) }; + + [UserSetting("P3B1 - 二运黑洞指挥模式标点优先级")] + public static BhPriStgEnum BhMarkPriority { get; set; } = BhPriStgEnum.HDT; + + public enum BhPriStgEnum + { + HDT, + HDT三麻取反, + THD, + 美式T2泥3, + 自定义 + } + + [UserSetting("P3B1 - 二运黑洞指挥模式标点优先级(自定义设置)")] + public static string BhMarkPriorityStr { get; set; } = "437658120"; + + [UserSetting("P3B1 - 二运黑洞策略")] + public static BhStgEnum BhStg { get; set; } = BhStgEnum.一人一根线; + public enum BhStgEnum + { + 一人一根线, + 攻1禁2接两根, + } + + [UserSetting("P3B2 - 冰封初始黄圈引导策略")] + public static BlizStgEnum BlizStg { get; set; } = BlizStgEnum.从上下引导后四角_盗火文档; + public enum BlizStgEnum + { + 从上下引导后四角_盗火文档, + 从场中引导后四角_POIKOS, + 从场中引导后上下_NoCCHH, + } + + [UserSetting("P3B2 - 以凯夫卡所在方位为北")] + public static bool BlizKefkaIsNorth { get; set; } = true; + + public void Init(ScriptAccessory sa) + { + _runId++; + sa.DebugMsg($"脚本 {Name} v{Version}{DebugVersion} 完成初始化,_runId {_runId}"); + + _pd.Init(sa, "初始化"); + _pdCaptain.Init(sa, "初始化"); + + _udmP3Param.Reset(sa); + sa.Method.RemoveDraw(".*"); + + if (P3B1CaptainMode) + sa.Method.MarkClear(); + sa.Method.ClearFrameworkUpdateAction(this); + } + + #region 测试项 + + [ScriptMethod(name: "———————— 《测试项》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 测试项分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "测试项:打印当前阶段", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 打印当前阶段(Event ev, ScriptAccessory sa) + { + sa.DebugMsg($"{_udmP3Param.当前阶段}"); + } + + [ScriptMethod(name: "测试项:展示优先级表格", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 展示优先级表格(Event ev, ScriptAccessory sa) + { + + var str = $"优先级字典:\n"; + foreach (var pair in _pd.Priorities) + { + str += $"Key {pair.Key} Value {pair.Value}\n"; + } + sa.DebugMsg($"\n{str}"); + sa.DebugMsg($"\n{_pd.ShowPriorities()}"); + } + + + [ScriptMethod(name: "测试项:双Boss赋值", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void 双Boss赋值(Event ev, ScriptAccessory sa) + { + _udmP3Param.ObjectId_卡奥斯 = sa.GetByDataId(19508u).First().GameObjectId; + _udmP3Param.ObjectId_艾克斯迪司 = sa.GetByDataId(19509u).First().GameObjectId; + } + + [ScriptMethod(name: "测试项:黑洞指挥模式测试 /e kdyhd", eventType: EventTypeEnum.Chat, eventCondition: ["Type:Echo", "Message:kdyhd"], + userControl: true)] + public void 黑洞指挥模式测试(Event ev, ScriptAccessory sa) + { + if (!P3B1CaptainMode) return; + lock (this) + { + _pdCaptain.Init(sa, "测试"); + + var (priority, normalized) = 构筑指挥优先级字段(BhMarkPriority); + if (BhMarkPriority == BhPriStgEnum.自定义) + sa.Method.SendChat($"/e 输入值:{BhMarkPriorityStr}"); + sa.Method.SendChat($"/e 实际作用值:{normalized}"); + _pdCaptain.AddPriorities(priority); + + for (int i = 0; i < _pdCaptain.Priorities.Count; i++) + { + var kvp = _pdCaptain.SelectSpecificPriorityIndex(i); + var marker = GetMarkTypeByRankBh(i, _udmP3Param.黑洞三麻取反); + sa.MarkPlayerByIdx(kvp.Key, marker); + sa.Method.SendChat($"/e 可达鸭光明正大地给 {sa.GetPlayerJobByIndex(kvp.Key)} 标上了 {marker}"); + } + } + + } + + #endregion 测试项 + + [ScriptMethod(name: "———————— 《P3》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: Debugging)] + public void P3_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3_分P_重构", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:47842"], + userControl: Debugging)] + public void P3_分P_重构(Event ev, ScriptAccessory sa) + { + _udmP3Param.当前阶段 = 3000; + sa.DebugMsg($"当前阶段为:P3 重构 {_udmP3Param.当前阶段}"); + } + + [ScriptMethod(name: "*P3B_重构时恢复凯夫卡透明度", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47842)$"], userControl: Debugging)] + public void P3B_重构时恢复凯夫卡透明度(Event ev, ScriptAccessory sa) + { + var obj = sa.GetById(ev.SourceId); + sa.AlphaModify(obj, 1f, currentAlpha => currentAlpha <= 0.8f); + } + + [ScriptMethod(name: "P3_获得艾克斯迪司ObjId", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:49891"], + userControl: Debugging)] + public void P3_获得艾克斯迪司ObjId(Event ev, ScriptAccessory sa) + { + _udmP3Param.ObjectId_艾克斯迪司 = ev.SourceId; + sa.DebugMsg($"{_udmP3Param.ObjectId_艾克斯迪司:X8}"); + + // 顺便恢复透明度 + var obj = sa.GetById(ev.SourceId); + sa.AlphaModify(obj, 1f, currentAlpha => currentAlpha <= 0.8f); + } + + [ScriptMethod(name: "P3_获得卡奥斯ObjId", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:49890"], + userControl: Debugging)] + public void P3_获得卡奥斯ObjId(Event ev, ScriptAccessory sa) + { + _udmP3Param.ObjectId_卡奥斯 = ev.SourceId; + sa.DebugMsg($"{_udmP3Param.ObjectId_卡奥斯:X8}"); + + // 顺便恢复透明度 + var obj = sa.GetById(ev.SourceId); + sa.AlphaModify(obj, 1f, currentAlpha => currentAlpha <= 0.8f); + } + + [ScriptMethod(name: "*P3_防火墙_屏蔽无效目标", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:49890"], + userControl: true)] + public void P3_防火墙_屏蔽无效目标(Event ev, ScriptAccessory sa) + { + if (!SpecialMode) return; + if (_udmP3Param.防火墙已开启) return; + + _udmP3Param.防火墙已开启 = true; + _udmP3Param.判断防火墙目标Framework = sa.Method.RegistFrameworkUpdateAction(Action); + const uint chaos_buff = 4192; + const uint exdeath_buff = 4194; + + const int 应战卡奥斯 = 1; + const int 应战艾克斯迪司 = 2; + const int 应战双人 = 3; + const int 没有应战 = 4; + + void Action() + { + var myObject = sa.Data.MyObject; + if (myObject is null) return; + + int currentState = (myObject.HasStatus(chaos_buff), myObject.HasStatus(exdeath_buff)) switch + { + (true, false) => 应战卡奥斯, + (false, true) => 应战艾克斯迪司, + (false, false) => 没有应战, + _ => 应战双人 + }; + + if (sa.GetMyIndex() <= 1) + { + // 坦克职业不参与 + currentState = 应战双人; + } + + if (currentState == _udmP3Param.上一次防火墙状态 && _udmP3Param.上一次防火墙状态 != 0) return; + _udmP3Param.上一次防火墙状态 = currentState; + + switch (currentState) + { + case 应战卡奥斯: + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_卡奥斯), true); + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_艾克斯迪司), false); + break; + case 应战艾克斯迪司: + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_卡奥斯), false); + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_艾克斯迪司), true); + break; + default: + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_卡奥斯), true); + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_艾克斯迪司), true); + break; + }; + } + } + + [ScriptMethod(name: "P3_狂暴前解除防火墙", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47878", "TargetIndex:1"], userControl: Debugging)] + public void P3_狂暴前解除防火墙(Event ev, ScriptAccessory sa) + { + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_卡奥斯), true); + sa.SetTargetable(sa.GetById(_udmP3Param.ObjectId_艾克斯迪司), true); + _udmP3Param.防火墙已开启 = false; + } + + [ScriptMethod(name: "P3_根据Buff判断MTST", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(419[24])$"], + userControl: Debugging)] + public void P3_根据Buff判断MTST(Event ev, ScriptAccessory sa) + { + const uint chaos_buff = 4192; + const uint exdeath_buff = 4194; + + var idx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (idx is < 0 or >= 2) return; + + var statusId = ev.StatusId; + + _udmP3Param.拉艾克斯迪司的是MT = idx switch + { + 0 => statusId == exdeath_buff, + 1 => statusId == chaos_buff + }; + sa.DebugMsg( + $"{sa.GetPlayerJobByIndex(idx)} 获得 {statusId},拉艾克斯迪司的是 {(_udmP3Param.拉艾克斯迪司的是MT ? "MT" : "ST")}"); + } + + #region P3A 深层痛楚 通用部分 + + [ScriptMethod(name: "=============《P3A 深层痛楚》=============", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3A_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A_一运分P_深层痛楚", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:47858"], + userControl: Debugging)] + public void P3A_一运分P_深层痛楚(Event ev, ScriptAccessory sa) + { + _pd.Init(sa, "P3一运"); + _pd.AddPriorities([1, 2, 3, 4, 5, 6, 7, 8]); + _udmP3Param.当前阶段 = 3100; + sa.DebugMsg($"当前阶段为:P3 一运 深层痛楚 {_udmP3Param.当前阶段}"); + } + + + [ScriptMethod(name: "P3A_状态添加记录", eventType: EventTypeEnum.StatusAdd, + eventCondition: ["StatusID:regex:^(160[0123])$", "SourceId:E0000000"], + userControl: Debugging)] + public void P3A_状态添加(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3100) return; + const uint 混沌之炎 = 1600; + const uint 混沌之水 = 1601; + const uint 混沌之风 = 1602; + const uint 混沌之逆风 = 1603; + + lock (_pd) + { + var statusId = ev.StatusId; + var playerIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var priVal = ev.StatusId switch + { + 混沌之炎 => 100, + 混沌之水 => 200, + 混沌之风 => 10, + 混沌之逆风 => 20, + _ => 0 + }; + + _pd.AddPriority(playerIdx, priVal); + _udmP3Param.一运状态记录次数++; + sa.DebugMsg($"记录到状态 {statusId} / {priVal} 于角色 {Role[playerIdx]}"); + + if (_udmP3Param.一运状态记录完毕()) + _udmP3Param.一运状态记录.Set(); + + if (statusId != 混沌之炎) return; + var statusTime = ev.DurationMilliseconds(); + _udmP3Param.是长火 = statusTime > 30000; + } + } + + [ScriptMethod(name: "P3A_水晶生成记录", eventType: EventTypeEnum.ObjectChanged, + eventCondition: ["Operate:Add", "DataId:regex:^(201529[012])$"], + userControl: Debugging)] + public void P3A_水晶生成记录(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3100) return; + const uint 火水晶 = 2015290; + const uint 水水晶 = 2015291; + const uint 风水晶 = 2015292; + + var region = ev.SourcePosition.GetRadian(Center).RadianToRegion(4); + var dataId = ev.DataId(); + + switch (dataId) + { + case 火水晶: + _udmP3Param.火水晶方位 = region; + break; + case 水水晶: + _udmP3Param.水水晶方位 = region; + break; + case 风水晶: + _udmP3Param.风水晶方位 = region; + break; + } + sa.DebugMsg($"记录到水晶 {dataId} 于方位 {region}"); + + if (!_udmP3Param.水晶状态记录完毕()) return; + _udmP3Param.无水晶方位 = 6 - _udmP3Param.火水晶方位 - _udmP3Param.水水晶方位 - _udmP3Param.风水晶方位; + _udmP3Param.一运水晶记录.Set(); + } + + [ScriptMethod(name: "P3A_暴雷死刑范围", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47881"], userControl: true)] + public void P3A_暴雷死刑范围(Event ev, ScriptAccessory sa) + { + var dp = sa.DrawCircle(ev.SourceId, 0, 8000, $"靠近死刑", 5f, sa.Data.DefaultDangerColor.WithW(2f), draw: false); + dp.SetOwnersDistanceOrder(true, 1); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + [ScriptMethod(name: "P3A_暴雷死刑目标连线", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47881"], userControl: true)] + public void P3A_暴雷死刑目标连线(Event ev, ScriptAccessory sa) + { + var color = new Vector4(0.1f, 1f, 1f, 1f); + var dp = sa.DrawLine(ev.SourceId, 0, 0, 8000, $"艾克斯迪司与死刑目标连线", 0, 1, 40, color, byY: true, draw: false); + dp.TargetResolvePattern = PositionResolvePatternEnum.PlayerNearestOrder; + dp.TargetOrderIndex = 1; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp); + } + + [ScriptMethod(name: "P3A_暴雷死刑通用提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47881"], userControl: true)] + public void P3A_暴雷死刑通用提示(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 <= 3101) return; + sa.Method.TextInfo($"艾克斯迪司 靠近死刑", 3000); + sa.Method.TTS($"艾克斯迪司 靠近死刑", 3); + } + + [ScriptMethod(name: "P3A_经纬聚爆", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47869|47870)$"], userControl: true)] + public void P3A_经纬聚爆第一段(Event ev, ScriptAccessory sa) + { + const uint 纬度聚爆 = 47870; // 先打左右 + const uint 经度聚爆 = 47869; // 先打上下 + _udmP3Param.绘制经纬聚爆范围 = true; + _udmP3Param.经纬聚爆第二段绘制完毕 = false; + _udmP3Param.经纬聚爆先打左右 = ev.ActionId == 纬度聚爆; + _udmP3Param.ObjectId_卡奥斯 = ev.SourceId; + + // 第一轮 + sa.DrawFan(ev.SourceId, 0, 10000, $"第一轮前左", 90f.DegToRad(), (_udmP3Param.经纬聚爆先打左右 ? 90f : 0f).DegToRad(), 60, 0, sa.Data.DefaultDangerColor.WithW(2f)); + sa.DrawFan(ev.SourceId, 0, 10000, $"第一轮后右", 90f.DegToRad(), (_udmP3Param.经纬聚爆先打左右 ? -90f : 180f).DegToRad(), 60, 0, sa.Data.DefaultDangerColor.WithW(2f)); + } + + [ScriptMethod(name: "P3A_经纬聚爆第二段与删除绘图", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(47871)$"], suppress: 500, userControl: true)] + public void P3A_经纬聚爆第二段与删除绘图(Event ev, ScriptAccessory sa) + { + if (!_udmP3Param.绘制经纬聚爆范围) return; + + if (_udmP3Param.经纬聚爆第二段绘制完毕) + sa.Method.RemoveDraw($"第二轮[前左|后右]"); + else + { + sa.Method.RemoveDraw($"第一轮[前左|后右]"); + _udmP3Param.经纬聚爆第二段绘制完毕 = true; + sa.DrawFan(_udmP3Param.ObjectId_卡奥斯, 0, 10000, $"第二轮前左", 90f.DegToRad(), (!_udmP3Param.经纬聚爆先打左右 ? 90f : 0f).DegToRad(), 60, 0, sa.Data.DefaultDangerColor.WithW(2f)); + sa.DrawFan(_udmP3Param.ObjectId_卡奥斯, 0, 10000, $"第二轮后右", 90f.DegToRad(), (!_udmP3Param.经纬聚爆先打左右 ? -90f : 180f).DegToRad(), 60, 0, sa.Data.DefaultDangerColor.WithW(2f)); + } + } + + #endregion P3A 深层痛楚 通用部分 + + #region P3A1 一水火 3100 + + [ScriptMethod(name: "———————— 《P3A1 一水火》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3A1_一水火分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A_暴雷钢铁", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47890"], userControl: true)] + public void P3A_暴雷钢铁(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3100) return; + _udmP3Param.ObjectId_艾克斯迪司 = ev.SourceId; + sa.DrawCircle(ev.SourceId, 0, 7000, $"P3A_暴雷钢铁 暴雷钢铁", 14.8f, sa.Data.DefaultDangerColor.WithW(2f), byTime: true); + } + + [ScriptMethod(name: "*P3A_移除暴雷连线", eventType: EventTypeEnum.VfxEvent, + eventCondition: ["Type:Channeling", "Id:64"], userControl: true)] + public async void P3A_移除暴雷连线(Event ev, ScriptAccessory sa) + { + if (!SpecialMode) return; + var runId = _runId; + var obj = sa.GetById(ev.TargetId); + sa.WriteVisible(obj, false); + await Task.Delay(7000); + if (runId != _runId) return; + sa.WriteVisible(obj, true); + } + + [ScriptMethod(name: "P3A_深层痛楚判定", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47858"], suppress: 10000, + userControl: Debugging)] + public void P3A_深层痛楚判定(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3100) return; + if (!_udmP3Param.一运状态记录.WaitOne(2000)) return; + sa.DebugMsg($"一运状态记录完毕"); + if (!_udmP3Param.一运水晶记录.WaitOne(2000)) return; + sa.DebugMsg($"一运水晶记录完毕"); + + _udmP3Param.一运状态记录.Reset(); + _udmP3Param.一运水晶记录.Reset(); + + _udmP3Param.成员分组(sa, _pd); + sa.DebugMsg($"成员分组完毕"); + + _udmP3Param.一水火准备.Set(); + } + + [ScriptMethod(name: "P3A_第一轮水火范围", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47858"], suppress: 10000, + userControl: true)] + public async void P3A_第一轮水火范围(Event ev, ScriptAccessory sa) + { + var runId = _runId; + + if (_udmP3Param.当前阶段 != 3100) return; + await Task.Delay(8000); + if (runId != _runId) return; + if (_udmP3Param.当前阶段 != 3100) return; + + DrawWaterFireRange(sa, _udmP3Param.一水火指路与绘图时间); + } + + [ScriptMethod(name: "P3A_一水火指路", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47858"], suppress: 10000, + userControl: true)] + public async void P3A_一水火指路(Event ev, ScriptAccessory sa) + { + var runId = _runId; + if (_udmP3Param.当前阶段 != 3100) return; + if (!_udmP3Param.一水火准备.WaitOne(2000)) return; + + sa.DebugMsg($"开始"); + var myIndex = sa.GetMyIndex(); + + // 艾克斯迪司 ST 无水晶 + if (myIndex == (_udmP3Param.拉艾克斯迪司的是MT ? 0 : 1)) + { + var aimRegion = BoAStg switch + { + BoAStgEnum.TLB_拉火水晶_夜音 => _udmP3Param.水水晶方位, + _ => _udmP3Param.无水晶方位 + }; + 执行P3拉怪指路逻辑(sa, "艾克斯迪司", aimRegion, 6500, false); + } + + // 卡奥斯 MT 正攻场中,逃课风水晶 + else if (myIndex == (_udmP3Param.拉艾克斯迪司的是MT ? 1 : 0)) + { + var toCenter = BoAStg == BoAStgEnum.正攻; + var aimRegion = BoAStg switch + { + BoAStgEnum.TLB_拉火水晶_夜音 => _udmP3Param.火水晶方位, + _ => _udmP3Param.风水晶方位 + }; + 执行P3拉怪指路逻辑(sa, "卡奥斯", aimRegion, 6500, toCenter); + } + + await Task.Delay(8000); + if (runId != _runId) return; + + 执行水火指路逻辑(sa, _udmP3Param.一水火指路与绘图时间); + _udmP3Param.一水火准备.Reset(); + } + + + + [ScriptMethod(name: "P3A_第二轮水火范围", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47869|47870)$"], suppress: 10000, userControl: true)] + public void P3A_第二轮水火范围(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3102 && _udmP3Param.当前阶段 != 3110) return; + DrawWaterFireRange(sa, _udmP3Param.二水火指路与绘图时间); + } + + private void DrawWaterFireRange(ScriptAccessory sa, int destroyTime) + { + // 通用绘图范围 + if (_udmP3Param.当前轮为火()) + { + DrawGroupCircle(sa, _udmP3Param.火组, "DrawWaterFireRange 火组", sa.Data.DefaultDangerColor, 5, destroyTime); + DrawNearDonut(sa, sa.Data.DefaultDangerColor, destroyTime + 3000); + } + else + { + DrawGroupDonut(sa, _udmP3Param.水组, "DrawWaterFireRange 水组", sa.Data.DefaultDangerColor, destroyTime); + DrawNearCircle(sa, sa.Data.DefaultDangerColor, destroyTime + 3000); + } + } + + private void 执行P3拉怪指路逻辑(ScriptAccessory sa, string bossStr, int region, int destroyTime, bool toCenter = false) + { + string regionStr; + Vector3 guidePos; + + if (toCenter) + { + guidePos = Center; + regionStr = "场中"; + } + else + { + guidePos = _udmP3Param.基于水晶旋转(CrystalBasePos, region); + regionStr = region switch + { + 0 => "右下", + 1 => "右上", + 2 => "左上", + _ => "左下" + }; + } + + sa.DrawGuidance(guidePos, 0, destroyTime, $"执行P3拉怪指路逻辑 {bossStr}", sa.Data.DefaultSafeColor); + sa.Method.TextInfo($"将 {bossStr} 拉到{regionStr}", 3000); + sa.Method.TTS($"将{bossStr}拉到{regionStr}", 3); + } + + private void 执行水火指路逻辑(ScriptAccessory sa, int destroyTime) + { + var myIndex = sa.GetMyIndex(); + var myPriVal = _pd.Priorities[myIndex]; + + var windIndex = _udmP3Param.查询组序(_udmP3Param.风组, myIndex); + var waterIndex = _udmP3Param.查询组序(_udmP3Param.水组, myIndex); + var fireIndex = _udmP3Param.查询组序(_udmP3Param.火组, myIndex); + + switch (BoAStg) + { + case BoAStgEnum.正攻: + if (windIndex >= 0) + DrawWindGuideAroundCrystal_Wf(sa, windIndex, destroyTime); + if (waterIndex >= 0) + DrawWaterGuideAlongCrystal_Wf(sa, waterIndex, myPriVal, destroyTime); + if (fireIndex >= 0) + DrawFireGuideAlongCrystal_Wf(sa, fireIndex, myPriVal, destroyTime); + break; + + case BoAStgEnum.TLB_相反水晶_盗火文档: + if (windIndex >= 0) + DrawWindGuideAroundCrystal_Wf(sa, windIndex, destroyTime); + if (waterIndex >= 0) + { + DrawToCrystal(sa, _udmP3Param.火水晶方位, destroyTime, $"水组去火"); + sa.Method.TextInfo($"靠近火水晶,集合放月环", 6000); + sa.Method.TTS($"靠近火水晶 集合放月环", 3); + } + + if (fireIndex >= 0) + { + DrawToCrystal(sa, _udmP3Param.水水晶方位, destroyTime, $"火组去水"); + sa.Method.TextInfo($"靠近水水晶,分散放钢铁", 6000); + sa.Method.TTS($"靠近水水晶 分散放钢铁", 3); + } + break; + + case BoAStgEnum.TLB_纯固定_NoCCHH: + DrawGuide_CCHH_Wf(sa, destroyTime); + DrawGuideLine_CCHH_Wf(sa, destroyTime); + break; + + case BoAStgEnum.TLB_拉火水晶_夜音: + if (fireIndex >= 0) + DrawFireGuide_YY_Wf(sa, fireIndex, destroyTime); + else + { + DrawToCrystal(sa, _udmP3Param.火水晶方位, destroyTime, $"风水去水"); + sa.Method.TextInfo($"原地处理月环", 6000); + sa.Method.TTS($"原地处理月环", 3); + } + break; + + // case BoAStgEnum.TLB_火炸人群_OverClock: + // break; + } + } + + private void DrawFireGuide_YY_Wf(ScriptAccessory sa, int fireIndex, int destroyTime) + { + float angleBias = fireIndex switch + { + 0 => -45f, + 1 => 45f, + _ => 45f + }; + var basePos = CrystalBasePos.RotateAndExtend(Center, 0, -10f); + var guidePos = basePos.RotateAndExtend(CrystalBasePos, angleBias.DegToRad()); + + if (_udmP3Param.当前轮为火()) + { + sa.Method.TextInfo($"远离 放火钢铁", 6000); + sa.Method.TTS($"远离放火钢铁", 3); + } + else + { + sa.Method.TextInfo($"远离 吃水钢铁", 6000); + sa.Method.TTS($"远离吃水钢铁", 3); + } + + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.火水晶方位); + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawFireGuide_YY_Wf 指路", sa.Data.DefaultSafeColor); + sa.DebugMsg($"玩家为火组第 {fireIndex},基于水晶逆时针旋转 {angleBias} deg"); + } + + private void DrawGuide_CCHH_Wf(ScriptAccessory sa, int destroyTime) + { + var myIndex = sa.GetMyIndex(); + float angleBias = myIndex switch + { + 0 => -135f, + 1 => 135f, + 4 => -45f, + 5 => 45f, + 2 => -30f, + 3 => 30f, + 6 => -75f, + 7 => 75f, + _ => 75f + }; + bool isMelee = (myIndex % 4) <= 1; + var basePos = CrystalBasePos.RotateAndExtend(Center, 0, isMelee ? -4f : -14.5f); + var guidePos = basePos.RotateAndExtend(CrystalBasePos, angleBias.DegToRad()); + + // 检查近战组是否有水 Buff,且该轮为水,则站脚下等月环 + if (!_udmP3Param.当前轮为火() && _udmP3Param.水组有近战(sa) && isMelee) + { + guidePos = CrystalBasePos; + sa.Method.TextInfo($"集合放月环", 6000); + sa.Method.TTS($"集合放月环", 3); + } + else + { + sa.Method.TextInfo($"避开钢铁月环", 6000); + sa.Method.TTS($"避开钢铁月环", 3); + } + + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.风水晶方位); + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawGuide_CCHH_Wf 指路", sa.Data.DefaultSafeColor); + } + + private void DrawGuideLine_CCHH_Wf(ScriptAccessory sa, int destroyTime) + { + var myIndex = sa.GetMyIndex(); + bool isMelee = (myIndex % 4) <= 1; + var windCrystalPos = _udmP3Param.基于水晶旋转(CrystalBasePos, _udmP3Param.风水晶方位); + var baseRadian = 180f.DegToRad() + windCrystalPos.GetRadian(Center); + if (isMelee) + { + List biasDeg = [-135, 135, -45, 45]; + for (int i = 0; i < 4; i++) + { + var dp = sa.DrawLine(windCrystalPos, 0, 0, destroyTime, $"DrawStaticGuideLine 指引线{i}", + baseRadian + biasDeg[i].DegToRad(), 20f, 50f, sa.Data.DefaultSafeColor, draw: false); + dp.Color = i switch + { + 0 or 1 => new Vector4(0.1f, 0.1f, 1f, 1), // Tank + _ => new Vector4(1, 0.1f, 0.1f, 1), // DPS + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + } + else + { + List biasDeg = [-30, 30, -75, 75]; + for (int i = 0; i < 4; i++) + { + var dp = sa.DrawLine(windCrystalPos, 0, 0, destroyTime, $"DrawStaticGuideLine 指引线{i}", + baseRadian + biasDeg[i].DegToRad(), 20f, 50f, sa.Data.DefaultSafeColor, draw: false); + dp.Color = i switch + { + 0 or 1 => new Vector4(0.1f, 1f, 0.1f, 1), // Healer + _ => new Vector4(1, 0.1f, 0.1f, 1), // DPS + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + } + } + + private void DrawWindGuideAroundCrystal_Wf(ScriptAccessory sa, int windIndex, int destroyTime) + { + List angleBias = [-30, -15, 15, 30]; + var guidePos = CrystalBasePos.RotateAndExtend(Center, angleBias[windIndex].DegToRad()); + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.风水晶方位); + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawWindGuideByBuffAndIndex_Wf 风组指路", sa.Data.DefaultSafeColor); + var dir = windIndex >= 2 ? "右" : "左"; + sa.Method.TextInfo(BoAStg == BoAStgEnum.正攻 ? $"面向场中,前往 风水晶{dir} 吃分摊" : $"前往 风水晶", 6000); + sa.Method.TTS(BoAStg == BoAStgEnum.正攻 ? $"前往 风水晶{dir} 吃分摊" : $"前往风水晶", 3); + } + + private void DrawWaterGuideAlongCrystal_Wf(ScriptAccessory sa, int waterIndex, int myPriVal, int destroyTime) + { + Vector3 guidePos; + if (_udmP3Param.当前轮为火()) + { + guidePos = Center; + DrawCrystalSightAvoid(sa, _udmP3Param.火水晶方位, myPriVal, destroyTime); + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.水水晶方位); + + var needBackStr = _udmP3Param.需要背对(myPriVal) ? "背对" : "面向"; + sa.Method.TextInfo($"前往场中 {needBackStr}火水晶 吃火", 6000); + sa.Method.TTS($"前往场中{needBackStr}火水晶吃火", 3); + } + else + { + List distanceBias = [-10, 4]; + guidePos = CrystalBasePos.RotateAndExtend(Center, 0, distanceBias[waterIndex]); + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.水水晶方位); + + var dir = waterIndex >= 1 ? "外" : "内"; + sa.Method.TextInfo($"前往 水水晶{dir} 准备判定", 6000); + sa.Method.TTS($"前往 水水晶{dir} 准备判定", 3); + } + + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawWaterGuideAlongCrystal_Wf 水组指路", sa.Data.DefaultSafeColor); + } + + private void DrawFireGuideAlongCrystal_Wf(ScriptAccessory sa, int fireIndex, int myPriVal, int destroyTime) + { + Vector3 guidePos; + if (_udmP3Param.当前轮为火()) + { + List distanceBias = [-10, 4]; + guidePos = CrystalBasePos.RotateAndExtend(Center, 0, distanceBias[fireIndex]); + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.火水晶方位); + + var dir = fireIndex >= 1 ? "外" : "内"; + sa.Method.TextInfo($"前往 火水晶{dir} 准备判定", 6000); + sa.Method.TTS($"前往 火水晶{dir} 准备判定", 3); + + } + else + { + guidePos = Center; + DrawCrystalSightAvoid(sa, _udmP3Param.水水晶方位, myPriVal, destroyTime); + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.火水晶方位); + + var needBackStr = _udmP3Param.需要背对(myPriVal) ? "背对" : "面向"; + sa.Method.TextInfo($"前往场中 {needBackStr}水水晶 吃火", 6000); + sa.Method.TTS($"前往场中{needBackStr}水水晶吃火", 3); + } + + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawFireGuideWf 火组指路", sa.Data.DefaultSafeColor); + } + + private void DrawToCrystal(ScriptAccessory sa, int crystalRegion, int destroyTime, string name) + { + var guidePos = _udmP3Param.基于水晶旋转(CrystalBasePos, crystalRegion); + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawToCrystal {name}", sa.Data.DefaultSafeColor); + } + + private void DrawCrystalSightAvoid(ScriptAccessory sa, int crystalRegion, int myPriVal, int destroyTime) + { + var needBack = _udmP3Param.需要背对(myPriVal); + var sightRegion = crystalRegion + (needBack ? 0 : 2); + DrawFacingArrow(sa, sightRegion, false, $"DrawCrystalSightAvoid 面向辅助自身", destroyTime); + DrawFacingArrow(sa, sightRegion, true, $"DrawCrystalSightAvoid 面向辅助正确面向", destroyTime); + } + + void DrawFacingArrow(ScriptAccessory sa, int dir, bool isSupport, string name, int destroyTime) + { + var dp = sa.DrawLine(sa.Data.Me, 0, 0, destroyTime, name, + isSupport ? (45f + dir * 90f).DegToRad() : 0, 1f, 4.5f, + isSupport ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor, draw: false); + dp.FixRotation = isSupport; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + } + + private void DrawGroupCircle( + ScriptAccessory sa, List> group, string name, Vector4 color, float scale, int destroyTime) + { + for (var i = 0; i < group.Count; i++) + sa.DrawCircle(sa.Data.PartyList[group[i].Key], 0, destroyTime, $"{name}钢铁{i + 1}", scale, color); + } + + private void DrawGroupDonut( + ScriptAccessory sa, List> group, string name, Vector4 color, int destroyTime) + { + for (var i = 0; i < group.Count; i++) + sa.DrawDonut(sa.Data.PartyList[group[i].Key], 0, destroyTime, $"{name}月环{i + 1}", 10, 5, color); + } + + private void DrawNearCircle(ScriptAccessory sa, Vector4 color, int destroyTime) + { + var crystalPos = new Vector3(109.9f, 0, 109.9f); + crystalPos = _udmP3Param.基于水晶旋转(crystalPos, _udmP3Param.水水晶方位); + + for (int i = 0; i < 2; i++) + { + var dp = sa.DrawCircle(crystalPos, 0, destroyTime, $"DrawNearCircle 水水晶钢铁{i + 1}", 5f, color, draw: false); + dp.SetOwnersDistanceOrder(true, (uint)(i + 1)); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + } + + private void DrawNearDonut(ScriptAccessory sa, Vector4 color, int destroyTime) + { + var crystalPos = new Vector3(109.9f, 0, 109.9f); + crystalPos = _udmP3Param.基于水晶旋转(crystalPos, _udmP3Param.火水晶方位); + + for (int i = 0; i < 2; i++) + { + var dp = sa.DrawDonut(crystalPos, 0, destroyTime, $"DrawNearCircle 火水晶月环{i + 1}", 10f, 5f, color, draw: false); + dp.SetOwnersDistanceOrder(true, (uint)(i + 1)); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); + } + } + + + #endregion P3A1 一水火 3100 + + #region P3A1 暴雷死刑 3101 3102 + + [ScriptMethod(name: "P3A_暴雷一死刑提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47881"], userControl: true)] + public void P3A_暴雷一死刑提示(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3100) return; + _udmP3Param.当前阶段 = 3101; + sa.DebugMsg($"暴雷死刑,一水火结束"); + + if (sa.GetMyIndex() != (_udmP3Param.拉艾克斯迪司的是MT ? 0 : 1)) return; + sa.DrawGuidance(ev.TargetId, 0, 5000, $"P3A_暴雷_死刑", sa.Data.DefaultSafeColor); + sa.Method.TextInfo($"前往 艾克斯迪司 脚下吃死刑", 3000); + sa.Method.TTS($"前往艾克斯迪司脚下吃死刑", 3); + } + + [ScriptMethod(name: "P3A_暴雷死刑后_拉Boss提示", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47884"], suppress: 10000, userControl: true)] + public void P3A_暴雷死刑后_拉Boss提示(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3101) return; + _udmP3Param.当前阶段 = 3102; + + + // 非正攻情况下,死刑后,艾克斯迪司回人群 + if (sa.GetMyIndex() == (_udmP3Param.拉艾克斯迪司的是MT ? 0 : 1)) + { + if (BoAStg == BoAStgEnum.正攻) return; + var aimRegion = BoAStg switch + { + BoAStgEnum.TLB_拉火水晶_夜音 => _udmP3Param.火水晶方位, + _ => _udmP3Param.风水晶方位 + }; + 执行P3拉怪指路逻辑(sa, "艾克斯迪司", aimRegion, 4000, false); + sa.DebugMsg($"暴雷死刑结束,ST 拉艾克斯迪司"); + } + + // 正攻情况下,死刑后,卡奥斯去场中 + if (sa.GetMyIndex() == (_udmP3Param.拉艾克斯迪司的是MT ? 1 : 0)) + { + if (BoAStg != BoAStgEnum.正攻) return; + 执行P3拉怪指路逻辑(sa, "卡奥斯", 0, 4000, true); + sa.DebugMsg($"暴雷死刑结束,MT 拉卡奥斯去场中"); + } + } + + #endregion P3A1 暴雷死刑 3101 3102 + + #region P3A1 二水火 3110 + + [ScriptMethod(name: "———————— 《P3A1 二水火》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3A1_二水火分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A_二水火阶段推进", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47869|47870)$"], suppress: 10000, userControl: Debugging)] + public void P3A_二水火阶段推进(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3102) return; + _udmP3Param.当前阶段 = 3110; + sa.DebugMsg($"二水火开始,转阶段 {_udmP3Param.当前阶段}"); + } + + [ScriptMethod(name: "P3A_二水火指路", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47869|47870)$"], suppress: 10000, userControl: true)] + public void P3A_二水火指路(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3102 && _udmP3Param.当前阶段 != 3110) return; + sa.DebugMsg($"开始"); + 执行水火指路逻辑(sa, _udmP3Param.二水火指路与绘图时间); + } + + [ScriptMethod(name: "P3A_二水火结束阶段推进", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(4786[01])$"], suppress: 10000, userControl: Debugging)] + public void P3A_二水火结束阶段推进(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3110) return; + _udmP3Param.当前阶段 = 3111; + sa.DebugMsg($"二水火结束,转阶段 {_udmP3Param.当前阶段}"); + } + + [ScriptMethod(name: "P3A_二水火结束拉怪提示", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(4786[01])$"], suppress: 10000, userControl: true)] + public void P3A_二水火结束(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3110 && _udmP3Param.当前阶段 != 3111) return; + + /* + * 混沌之水月环 混沌之水 47862 + * 混沌之水引发的钢铁 海啸 47861 + * 混沌之火钢铁 混沌之炎 47859 + * 混沌之火引发的月环 烈焰 47860 + * 混沌之风引发的分摊 龙卷风 47864 + */ + + // 根据海啸/烈焰与当前阶段判断二水火的结束 + sa.DebugMsg($"二水火结束,当前阶段 {_udmP3Param.当前阶段}"); + + var myIndex = sa.GetMyIndex(); + if (myIndex <= 1) + 执行二水火后拉怪指路逻辑(sa, true); + else if (myIndex == 6) + 执行二水火后引导指路逻辑(sa); + else + 执行二水火后拉怪指路逻辑(sa, false); + } + + private void 执行二水火后拉怪指路逻辑(ScriptAccessory sa, bool isTank) + { + var aimRegion = BoAStg switch + { + BoAStgEnum.TLB_拉火水晶_夜音 => _udmP3Param.火水晶方位, + _ => _udmP3Param.风水晶方位 + }; + var guidePos = _udmP3Param.基于水晶旋转(CrystalBasePos, aimRegion); + + sa.DrawGuidance(guidePos, 0, 6500, $"执行二水火后拉怪指路逻辑 拉怪", sa.Data.DefaultSafeColor); + var dirStr = aimRegion switch + { + 0 => "右下", + 1 => "右上", + 2 => "左上", + _ => "左下" + }; + sa.Method.TextInfo(isTank ? $"将 双BOSS 拉到{dirStr}" : $"于 {dirStr} 集合", 3000); + sa.Method.TTS(isTank ? $"将双BOSS拉到{dirStr}" : $"于 {dirStr} 集合", 3); + } + + private void 执行二水火后引导指路逻辑(ScriptAccessory sa) + { + var aimRegion = BoAStg switch + { + // 火对面是水 + BoAStgEnum.TLB_拉火水晶_夜音 => _udmP3Param.水水晶方位, + // 风对面是无 + _ => _udmP3Param.无水晶方位 + }; + + var guidePos = _udmP3Param.基于水晶旋转(CrystalBasePos, aimRegion); + sa.DrawGuidance(guidePos, 0, 6500, $"执行二水火后引导指路逻辑 引导", sa.Data.DefaultSafeColor); + var dirStr = aimRegion switch + { + 0 => "右下", + 1 => "右上", + 2 => "左上", + _ => "左下" + }; + sa.Method.TextInfo($"去{dirStr} 引导超级跳", 3000); + sa.Method.TTS($"去{dirStr}引导超级跳", 3); + } + + #endregion P3A1 二水火 3110 + + #region P3A1 真空波 3111 + + [ScriptMethod(name: "———————— 《P3A1 真空波》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3A1_真空波分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A_真空波", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47891)$"], userControl: true)] + public void P3A_真空波(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3111) return; + sa.DebugMsg($"开始"); + + if (BoAStg == BoAStgEnum.正攻) + 执行真空波正攻指路逻辑(sa); + else + // 其余逃课都是执行固定站位逻辑 + 执行真空波逃课指路逻辑(sa); + } + + [ScriptMethod(name: "*P3A_真空波读条期间卡奥斯透明", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47891)$"], userControl: true)] + public async void P3A_真空波读条期间卡奥斯透明(Event ev, ScriptAccessory sa) + { + if (!SpecialMode) return; + + if (_udmP3Param.当前阶段 != 3111) return; + sa.DebugMsg($"执行"); + + var runId = _runId; + var obj = sa.GetById(_udmP3Param.ObjectId_卡奥斯); + sa.AlphaModify(obj, 0.3f); + await Task.Delay(8000); + if (runId != _runId) return; + sa.AlphaModify(obj, 1f); + } + + [ScriptMethod(name: "P3A_真空波指引线", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47891)$"], userControl: true)] + public void P3A_真空波指引线(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3111) return; + DrawStaticGuideLineVw(sa, _udmP3Param.真空波指路时间); + } + + [ScriptMethod(name: "P3A_真空波后分摊", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(47891)$"], suppress: 10000, userControl: true)] + public void P3A_真空波后分摊(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3111) return; + sa.DebugMsg($"击退后分摊"); + + if (BoAStg == BoAStgEnum.正攻) + DrawGroupCircle(sa, _udmP3Param.风组, "P3A_真空波后分摊", sa.Data.DefaultSafeColor, 6, 4000); + else + { + var myIndex = sa.GetMyIndex(); + List partner = [2, 3, 0, 1]; + for (var i = 0; i < 4; i++) + sa.DrawCircle(sa.Data.PartyList[i + (myIndex < 4 ? 4 : 0)], 0, 4000, $"真空波分摊 {i}", 6, + i == partner[myIndex % 4] ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor); + } + } + + private void 执行真空波正攻指路逻辑(ScriptAccessory sa) + { + var myIndex = sa.GetMyIndex(); + var myPriVal = _pd.Priorities[myIndex]; + + // 水组火组站定,风组靠近等待击退 + var windIndex = _udmP3Param.查询组序(_udmP3Param.风组, myIndex); + if (windIndex >= 0) + { + DrawWindGuideVw(sa, windIndex, myPriVal, _udmP3Param.真空波指路时间); + return; + } + + var waterIndex = _udmP3Param.查询组序(_udmP3Param.水组, myIndex); + if (waterIndex >= 0) + { + DrawWaterFireGuideVw(sa, waterIndex, _udmP3Param.真空波指路时间, true); + return; + } + + var fireIndex = _udmP3Param.查询组序(_udmP3Param.火组, myIndex); + if (fireIndex >= 0) + { + DrawWaterFireGuideVw(sa, fireIndex, _udmP3Param.真空波指路时间, false); + return; + } + } + + private void DrawWindGuideVw(ScriptAccessory sa, int windIndex, int myPriVal, int destroyTime) + { + List angleBias = [60, 20, -20, -60]; + var basePos = CrystalBasePos.RotateAndExtend(Center, 0, -1f); + var guidePos = basePos.RotateAndExtend(CrystalBasePos, angleBias[windIndex].DegToRad(), 4f); + + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.风水晶方位); + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawWindGuideVw 风组指路", sa.Data.DefaultSafeColor); + + var needBack = _udmP3Param.需要背对(myPriVal); + var needBackStr = needBack ? "背对" : "面向"; + + DrawFacingArrow(sa, 0, false, $"DrawWindGuideVw 面向辅助自身", destroyTime); + var dp = sa.DrawLine(sa.Data.Me, _udmP3Param.ObjectId_艾克斯迪司, 0, destroyTime, $"DrawWindGuideVw 与艾克斯迪司连线", + (needBack ? 0 : 180f).DegToRad(), 1, 4.5f, sa.Data.DefaultSafeColor, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + sa.Method.TextInfo($"目标圈内 {needBackStr} 艾克斯迪司 击退", 4000); + sa.Method.TTS($"{needBackStr}Boss击退", 3); + } + + private void DrawWaterFireGuideVw(ScriptAccessory sa, int groupIndex, int destroyTime, bool isWater) + { + List angleBias = [20, 60]; + var basePos = CrystalBasePos.RotateAndExtend(Center, 0, -1f); + var guidePos = basePos.RotateAndExtend(CrystalBasePos, (isWater ? -1 : 1) * angleBias[groupIndex].DegToRad(), 10f); + guidePos = _udmP3Param.基于水晶旋转(guidePos, _udmP3Param.风水晶方位); + sa.DrawGuidance(guidePos, 0, destroyTime, $"DrawWaterFireGuideVw 水组指路", sa.Data.DefaultSafeColor); + + sa.Method.TextInfo($"防击退,然后与搭档分摊", 4000); + sa.Method.TTS($"防击退,然后与搭档分摊", 3); + } + + private void 执行真空波逃课指路逻辑(ScriptAccessory sa) + { + var myIndex = sa.GetMyIndex(); + + // 根据 role 固定站位 + List angleBias = [-60, 60, -20, 20, -20, 20, -60, 60]; + Vector3 bossPos = sa.GetById(_udmP3Param.ObjectId_艾克斯迪司).Position; + var basePos = bossPos.RotateAndExtend(Center, 0, -3.8f); + var guidePos = basePos.RotateAndExtend(bossPos, angleBias[myIndex].DegToRad()); + sa.DrawGuidance(guidePos, 0, _udmP3Param.真空波指路时间, $"执行真空波逃课指路逻辑 指路", sa.Data.DefaultSafeColor); + + // 面向指示 + var myPriVal = _pd.Priorities[myIndex]; + var needBack = _udmP3Param.需要背对(myPriVal); + var needBackStr = needBack ? "背对" : "面向"; + DrawFacingArrow(sa, 0, false, $"执行真空波逃课指路逻辑 面向辅助自身", _udmP3Param.真空波指路时间); + + var dp = sa.DrawLine(sa.Data.Me, _udmP3Param.ObjectId_艾克斯迪司, 0, _udmP3Param.真空波指路时间, $"执行真空波逃课指路逻辑 面向辅助正确面向", + needBack ? 180f.DegToRad() : 0, 1f, 4.5f, sa.Data.DefaultSafeColor, draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + + sa.Method.TextInfo($"目标圈内 {needBackStr} 艾克斯迪司 击退", 4000); + sa.Method.TTS($"{needBackStr}Boss击退", 3); + } + + private void DrawStaticGuideLineVw(ScriptAccessory sa, int destroyTime) + { + var myIndex = sa.GetMyIndex(); + Vector3 bossPos = sa.GetById(_udmP3Param.ObjectId_艾克斯迪司).Position; + var baseRadian = 180f.DegToRad() + bossPos.GetRadian(Center); + + List biasDeg = [-20, 20, -60, 60]; + List lineIdx = [2, 3, 0, 1, 0, 1, 2, 3]; + for (int i = 0; i < 4; i++) + { + var dp = sa.DrawLine(bossPos, 0, 0, destroyTime, $"DrawStaticGuideLineVw 指引线{i}", + baseRadian + biasDeg[i].DegToRad(), 20f, 50f, sa.Data.DefaultSafeColor, draw: false); + dp.Color = i == lineIdx[myIndex] ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + } + } + + [ScriptMethod(name: "*P3A1_真空波_自动面向辅助", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(47891)$"], userControl: Debugging)] + public void P3A1_真空波_自动面向辅助(Event ev, ScriptAccessory sa) + { + return; + if (_udmP3Param.当前阶段 != 3111) return; + if (!SpecialMode) return; + + sa.DebugMsg($"开始"); + + var myObject = sa.Data.MyObject; + if (myObject == null) return; + + // 判断进入条件 + const uint 混沌之风 = 1602; + const uint 混沌之逆风 = 1603; + var hasStatus = sa.Data.MyObject.HasStatusAny([混沌之风, 混沌之逆风]); + if (!hasStatus) return; + var myPriVal = _pd.Priorities[sa.GetMyIndex()]; + var needBack = _udmP3Param.需要背对(myPriVal); + + // 计算正确面向角度 + var bossObj = sa.GetById(_udmP3Param.ObjectId_艾克斯迪司); + if (bossObj == null) return; + var bossPos = bossObj.Position; + // 开启触发 + _udmP3Param.真空波面向辅助Framework = sa.Method.RegistFrameworkUpdateAction(Action); + return; + + void Action() + { + var myPos = myObject.Position; + var myRotation = myObject.Rotation; + var correctFaceRotation = myPos.GetRadian(bossPos) + (needBack ? 0 : float.Pi); + + var rotationDiff = MathF.Abs(myRotation.GetDiffRad(correctFaceRotation)); + if (!sa.IsMoving() && rotationDiff > 0.1f && (DateTime.Now - _udmP3Param.真空波面向辅助触发时间).TotalMilliseconds > 250) + { + _udmP3Param.真空波面向辅助触发时间 = DateTime.Now; + sa.SetRotation(myObject, correctFaceRotation, Debugging); + } + } + } + + [ScriptMethod(name: "*P3A1_真空波_自动面向辅助结束", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(47891)$"], suppress: 10000, userControl: Debugging)] + public void P3A1_真空波_自动面向辅助结束(Event ev, ScriptAccessory sa) + { + return; + if (_udmP3Param.当前阶段 != 3111) return; + sa.Method.UnregistFrameworkUpdateAction(_udmP3Param.真空波面向辅助Framework); + } + + #endregion P3A1 真空波 3111 + + #region P3A1 究极冲击波 3111 + + [ScriptMethod(name: "———————— 《P3A1 究极冲击波》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3A1_究极冲击波分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3A_究极冲击波方位记录", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:regex:^(47843)$"], suppress: 500, userControl: Debugging)] + public void P3A_究极冲击波方位记录(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3111) return; + if (_udmP3Param.究极冲击波记录完毕) return; + var region = ev.SourceRotation.RadianToRegion(8, 0, true); + if (_udmP3Param.究极冲击波起始方位 < 0) + { + _udmP3Param.究极冲击波起始方位 = region; + sa.DebugMsg($"起始方位 {region}"); + } + else + { + sa.DebugMsg($"第二轮方位 {region}"); + + int delta = (region - _udmP3Param.究极冲击波起始方位 + 8) % 8; + // 7 为顺时针,1 为逆时针 + _udmP3Param.究极冲击波为顺时针 = delta == 7; + _udmP3Param.究极冲击波记录完毕 = true; + + // 用面向计算的起始方位可直接作为终点,顺逆时针需要转换 + sa.DebugMsg($"为 {(_udmP3Param.究极冲击波为顺时针 ? "顺" : "逆")} 时针"); + } + } + + [ScriptMethod(name: "P3A_究极冲击波麻将记录", eventType: EventTypeEnum.TargetIcon, + eventCondition: ["Id:regex:^(015[0123]|01B[5678])$"], userControl: Debugging)] + public void P3A_究极冲击波麻将记录(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3111) return; + + lock (_pd) + { + var priVal = ev.Id0() switch + { + 0x0150 => 1000, + 0x0151 => 2000, + 0x0152 => 3000, + 0x0153 => 4000, + 0x01B5 => 5000, + 0x01B6 => 6000, + 0x01B7 => 7000, + _ => 8000, + }; + var playerIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + _pd.AddPriority(playerIdx, priVal); + sa.DebugMsg($"{Role[playerIdx]} 被点 {priVal / 1000}"); + } + } + + [ScriptMethod(name: "P3A_究极冲击波指路", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47864"], suppress: 1000, userControl: true)] + public void P3A_究极冲击波指路(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3111) return; + sa.DebugMsg($"风分摊结束,从方位 {_udmP3Param.究极冲击波起始方位} {(_udmP3Param.究极冲击波为顺时针 ? "逆" : "顺")} 开始"); + var priVal = _pd.Priorities[sa.GetMyIndex()]; + var num = priVal.GetDecimalDigit(3); + sa.DebugMsg($"玩家被点麻将 {priVal.GetDecimalDigit(3)}"); + if (num == 0) return; + + // 顺则逆转 + var baseRotDeg = _udmP3Param.究极冲击波起始方位 * 45f + 22.5f * (_udmP3Param.究极冲击波为顺时针 ? 1 : -1); + var basePos = new Vector3(100, 0, 119.5f).RotateAndExtend(Center, + baseRotDeg.DegToRad()); + var guidePos = basePos.RotateAndExtend(Center, 45f.DegToRad() * (num - 1) * (_udmP3Param.究极冲击波为顺时针 ? 1 : -1)); + sa.DrawGuidance(guidePos, 0, 12000, $"P3A_究极冲击波指路", sa.Data.DefaultSafeColor); + } + + #endregion P3A1 究极冲击波 3111 + + #region P3B 地震 通用部分 + + [ScriptMethod(name: "=============《P3B 地震》=============", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3B_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3B_二运分P_地震", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:50545"], + userControl: Debugging)] + public void P3A_二运分P_地震(Event ev, ScriptAccessory sa) + { + _pd.Init(sa, "P3二运"); + _pd.AddPriorities([1, 2, 3, 4, 5, 6, 7, 8]); + _udmP3Param.当前阶段 = 3200; + + sa.DebugMsg($"当前阶段为:P3 二运 地震 {_udmP3Param.当前阶段}"); + } + + // 根据不同类的输入重载一次 + private (List priority, string normalized) 构筑指挥优先级字段(BhPriStgEnum prioritySetting) + { + var priorityText = prioritySetting switch + { + BhPriStgEnum.THD => "12345678", + BhPriStgEnum.自定义 => BhMarkPriorityStr, + BhPriStgEnum.HDT三麻取反 => "437658120", + BhPriStgEnum.美式T2泥3 => "56781234", + _ => "43765812" + }; + + _udmP3Param.混沌泥土最末 = prioritySetting == BhPriStgEnum.美式T2泥3; + return 构筑指挥优先级字段(priorityText); + } + + private (List priority, string normalized) 构筑指挥优先级字段(string prioritySetting) + { + var normalized = (prioritySetting ?? "").Trim(); + + // 长度必须为 8 或 9,若长度为 9,最后一位必须是 0 + // 每一位都是 1~8,且互不相同 + var isValid = + (normalized.Length == 8 || (normalized.Length == 9 && normalized[8] == '0')) && + normalized[..8].All(c => c is >= '1' and <= '8') && + normalized[..8].Distinct().Count() == 8; + if (!isValid) + normalized = "437658120"; + + var roleIdxMap = new Dictionary + { + ['1'] = 0, + ['2'] = 1, + ['3'] = 2, + ['4'] = 3, + ['5'] = 4, + ['6'] = 5, + ['7'] = 6, + ['8'] = 7, + }; + var priority = new int[8]; + for (var i = 0; i < 8; i++) + // priority [职能] = 优先级,输入的"12345678"要再映射到"01(10)234567" + priority[roleIdxMap[normalized[i]]] = i + 1; + + _udmP3Param.黑洞三麻取反 = normalized.Length == 9 && normalized[8] == '0'; + return (priority.ToList(), normalized); + } + + [ScriptMethod(name: "*P3B_黑洞、冰封期间双Boss透明", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47867)$"], userControl: true)] + public void P3B_黑洞期间双Boss透明(Event ev, ScriptAccessory sa) + { + if (!SpecialMode) return; + + const uint 黑洞 = 47867; + var obj1 = sa.GetById(_udmP3Param.ObjectId_卡奥斯); + var obj2 = sa.GetById(_udmP3Param.ObjectId_艾克斯迪司); + sa.AlphaModify(obj1, 0.3f); + sa.AlphaModify(obj2, ev.ActionId == 黑洞 ? 0.75f : 0.3f); + } + + [ScriptMethod(name: "*P3B_凯夫卡上桌透明化", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(4784[67]|4785[235])$"], userControl: true)] + public async void P3B_凯夫卡上桌透明化(Event ev, ScriptAccessory sa) + { + if (!SpecialMode) return; + var runId = _runId; + var obj = sa.GetById(ev.SourceId); + sa.AlphaModify(obj, 0.3f); + await Task.Delay(10000); + if (runId != _runId) return; + sa.AlphaModify(obj, 1f); + } + + [ScriptMethod(name: "*P3B_卡奥斯非技能期间透明化", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47867|47869|47870)$"], userControl: true)] + public async void P3B_卡奥斯非技能期间透明化(Event ev, ScriptAccessory sa) + { + if (!SpecialMode) return; + if (_udmP3Param.当前阶段 != 3200) return; + + const uint 诅咒敕令 = 47867; + var awaitTime = ev.ActionId == 诅咒敕令 ? 5000 : 7500; + + var runId = _runId; + var obj = sa.GetById(_udmP3Param.ObjectId_卡奥斯); + sa.AlphaModify(obj, 1f, currentAlpha => currentAlpha <= 0.8f); + await Task.Delay(awaitTime); + if (runId != _runId) return; + sa.AlphaModify(obj, 0.5f, currentAlpha => currentAlpha >= 0.8f); + } + + [ScriptMethod(name: "*P3B_艾克斯迪司非技能期间透明化", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47881)$"], userControl: true)] + public async void P3B_艾克斯迪司非技能期间透明化(Event ev, ScriptAccessory sa) + { + if (!SpecialMode) return; + if (_udmP3Param.当前阶段 != 3200) return; + + var runId = _runId; + var obj = sa.GetById(_udmP3Param.ObjectId_艾克斯迪司); + sa.AlphaModify(obj, 1f, currentAlpha => currentAlpha <= 0.8f); + await Task.Delay(5000); + if (runId != _runId) return; + sa.AlphaModify(obj, 0.75f, currentAlpha => currentAlpha >= 0.8f); + } + + [ScriptMethod(name: "P3B_地震后拉回场中提示", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:50545"], + userControl: true, suppress: 10000)] + public void P3B_地震后拉回场中提示(Event ev, ScriptAccessory sa) + { + if (sa.GetMyIndex() > 1) return; + sa.DrawGuidance(Center, 0, 3000, $"P3B_地震后拉回场中", sa.Data.DefaultSafeColor); + sa.Method.TextInfo($"将 双BOSS 拉回场中", 3000); + sa.Method.TTS($"将双BOSS拉回场中", 3); + } + + [ScriptMethod(name: "P3B_响亮亮耳光范围与指路", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(4784[67])$"], userControl: true)] + public void P3B_响亮亮耳光范围与指路(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3200) return; + + const uint 右手接分摊 = 47846; + const uint 左手接职能 = 47847; + var aid = ev.ActionId; + var actionStr = aid switch + { + 右手接分摊 => "右手接分摊", + 左手接职能 => "左手接职能", + }; + + var isStack = aid == 右手接分摊; + + _udmP3Param.凯夫卡方位 = _udmP3Param.巨大凯夫卡方位获取(sa, true); + var (attackRegion, safeRegion) = _udmP3Param.巨大凯夫卡半场刀方向(sa, aid == 右手接分摊); + + // 画半场刀与钢铁范围 + sa.DrawCircle(Center, 0, 8000, $"[P3B_响亮亮耳光] 场中钢铁", 6f, sa.Data.DefaultDangerColor.WithW(2f)); + var biasCenter = (Center - new Vector3(0, 0, 3.8f)).RotateAndExtend(Center, -45f.DegToRad() * safeRegion); + sa.DrawRect(biasCenter, Center, 0, 7000, $"[P3B_响亮亮耳光] 半场刀", 0, 60f, 60f, sa.Data.DefaultDangerColor.WithW(2f)); + + // 画分摊或职能线 + List biasDeg = [0, -45f, 45f]; // H T D + var lineCount = isStack ? 1 : 3; + var myRole = sa.GetMyIndex() switch + { + 0 or 1 => 1, + 2 or 3 => 0, + _ => 2 + }; + + for (int i = 0; i < lineCount; i++) + { + var guidePos = biasCenter.RotateAndExtend(Center, biasDeg[i].DegToRad(), 3.5f); + var dp = sa.DrawLine(Center, guidePos, 0, 8000, $"响亮亮耳光 指引线{i}", + 0, 20f, 50f, sa.Data.DefaultSafeColor, draw: false); + dp.Color = i switch + { + 0 => new Vector4(0.1f, 1f, 0.1f, 1), // Healer + 1 => new Vector4(0.1f, 0.1f, 1f, 1), // Tank + 2 => new Vector4(1f, 0.1f, 0.1f, 1), // DPS + }; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Line, dp); + + if (myRole == i || isStack) + sa.DrawGuidance(guidePos, 0, 8000, $"响亮亮耳光 指路", sa.Data.DefaultSafeColor); + } + + sa.DebugMsg($"{actionStr}"); + sa.Method.TextInfo($"响亮亮耳光 {actionStr}", 3000); + sa.Method.TTS($"{actionStr}", 3); + } + + [ScriptMethod(name: "P3B_诅咒敕令半场刀", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47873)$"], userControl: true)] + public void P3B_诅咒敕令半场刀(Event ev, ScriptAccessory sa) + { + sa.DrawRect(ev.SourceId, 800, 4200, $"诅咒敕令半场刀", 0, 80, 30, CursedEdictColor.V4.WithW(CursedEdictWhiteVal), byTime: true); + } + + [ScriptMethod(name: "P3B_本色出演的我辣尾", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(47854)$"], userControl: true)] + public void P3B_本色出演的我辣尾(Event ev, ScriptAccessory sa) + { + var dp = sa.DrawRect(ev.SourceId, 0, 5000, $"本色出演的我辣尾", 0, 16, 100, sa.Data.DefaultDangerColor.WithW(2f), + draw: false); + dp.Offset = new Vector3(0, 0, 40); + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); + } + + [ScriptMethod(name: "P3B_白洞抬血提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:regex:^(48486)$"], userControl: true)] + public void P3B_白洞抬血提示(Event ev, ScriptAccessory sa) + { + if (sa.GetMyIndex() is not 2 and not 3) return; + sa.Method.TextInfo($"抬满全队!", 3000, true); + sa.Method.TTS($"抬满全队", 3); + } + + [ScriptMethod(name: "P3B_追踪巨大凯夫卡", eventType: EventTypeEnum.PlayActionTimeline, + eventCondition: ["Id:7748", "SourceDataId:19504"], userControl: true)] + public void P3B_追踪巨大凯夫卡(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3200) return; + var region = _udmP3Param.巨大凯夫卡方位获取(sa, false); + var kefkaPos = new Vector3(100, 0, 120); + kefkaPos = kefkaPos.RotateAndExtend(Center, region * 45f.DegToRad()); + var color = new Vector4(0.1f, 1f, 1f, 1f); + sa.DrawConnection(sa.Data.Me, kefkaPos, 0, 3000, $"跟踪巨大凯夫卡", color); + sa.Method.TextInfo($"凯夫卡重新出现了!", 1000); + } + + #endregion P3B 地震 通用部分 + + #region P3B1 黑洞 3200 + + [ScriptMethod(name: "———————— 《P3B1 黑洞》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3B1_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3B1_状态添加记录", eventType: EventTypeEnum.StatusAdd, + eventCondition: ["StatusID:regex:^(300[456]|1604)$", "SourceId:E0000000"], + userControl: Debugging)] + public void P3B_状态添加(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3200) return; + const uint 第一目标 = 3004; + const uint 第二目标 = 3005; + const uint 第三目标 = 3006; + const uint 混沌之泥土 = 1604; + + lock (_pdCaptain) + { + if (!_udmP3Param.获得过第一个黑洞状态) + { + _pdCaptain.Init(sa, "P3二运指挥"); + var (priority, _) = 构筑指挥优先级字段(BhMarkPriority); + _pdCaptain.AddPriorities(priority); + _udmP3Param.获得过第一个黑洞状态 = true; + } + + var statusId = ev.StatusId; + var playerIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + var priVal = ev.StatusId switch + { + 第一目标 => 100, + 第二目标 => 200, + 第三目标 => 300, + 混沌之泥土 => _udmP3Param.混沌泥土最末 ? 10 : 0, + _ => 0 + }; + + _pdCaptain.AddPriority(playerIdx, priVal); + _udmP3Param.二运状态记录次数++; + sa.DebugMsg($"记录到状态 {statusId} / {priVal} 于角色 {Role[playerIdx]}"); + + if (_udmP3Param.二运状态记录完毕()) + _udmP3Param.二运状态记录.Set(); + } + } + + [ScriptMethod(name: "P3B1_二运黑洞指挥标点", eventType: EventTypeEnum.StatusAdd, + eventCondition: ["StatusID:regex:^(300[456])$", "SourceId:E0000000"], + suppress: 10000, userControl: Debugging)] + public void P3B1_二运黑洞指挥标点(Event ev, ScriptAccessory sa) + { + if (!P3B1CaptainMode) return; + if (_udmP3Param.当前阶段 != 3200) return; + if (!_udmP3Param.二运状态记录.WaitOne(2000)) + 补齐黑洞状态优先级(sa); + + sa.DebugMsg($"进入指挥标点"); + sa.DebugMsg(_pdCaptain.ShowPriorities()); + for (int i = 0; i < 8; i++) + { + var kvp = _pdCaptain.SelectSpecificPriorityIndex(i); + var marker = GetMarkTypeByRankBh(i, _udmP3Param.黑洞三麻取反); + sa.MarkPlayerByIdx(kvp.Key, marker, localString: Debugging); + sa.DebugMsg($"给 {sa.GetPlayerJobByIndex(kvp.Key)} 标 {marker}"); + + if (P3B1CaptainModeDevHelper) + sa.Method.SendChat($"/e 可达鸭明目张胆地把 {marker} 标给了 {sa.GetPlayerJobByIndex(kvp.Key)}"); + } + _udmP3Param.二运状态记录.Reset(); + } + + private void 补齐黑洞状态优先级(ScriptAccessory sa) + { + // 通过修改 _pdCaptain 的值,伪造 Buff 出现在倒下的队友身上 + + var count_1 = 0; + var count_2 = 0; + var count_3 = 0; + List noBuffIdx = []; + + foreach (var kvp in _pdCaptain.Priorities) + { + if (kvp.Value >= 300) + count_3++; + else if (kvp.Value >= 200) + count_2++; + else if (kvp.Value >= 100) + count_1++; + else + { + noBuffIdx.Add(kvp.Key); + sa.DebugMsg($"检测到 {sa.GetPlayerJobByIndex(kvp.Key)} 没有获得状态"); + } + + } + + for (int i = 0; i < noBuffIdx.Count; i++) + { + var addVal = 输出补救模式优先级增加值(count_1, count_2, count_3); + + if (addVal == 300) + count_3++; + else if (addVal == 200) + count_2++; + else if (addVal == 100) + count_1++; + else + { + sa.DebugMsg($"输出补救模式优先级增加值 输入统计值错误 {count_1} {count_2} {count_3}"); + continue; + } + + _pdCaptain.Priorities[noBuffIdx[i]] += addVal; + sa.DebugMsg($"黑洞状态添加补救 令 {sa.GetPlayerJobByIndex(noBuffIdx[i])} 被点麻将 {addVal / 10}"); + } + } + + private int 输出补救模式优先级增加值(int count_1, int count_2, int count_3) + { + if (count_1 < 3) return 100; + if (count_2 < 3) return 200; + if (count_3 < 2) return 300; + return 0; + } + + private MarkType GetMarkTypeByRankBh(int rank, bool reverseThirdMarker) + { + return rank switch + { + 0 => MarkType.Attack1, + 1 => MarkType.Attack2, + 2 => MarkType.Attack3, + 3 => MarkType.Bind1, + 4 => MarkType.Bind2, + 5 => MarkType.Bind3, + 6 => reverseThirdMarker ? MarkType.Stop2 : MarkType.Stop1, + _ => reverseThirdMarker ? MarkType.Stop1 : MarkType.Stop2 + }; + } + + [ScriptMethod(name: "P3B1_获得二运黑洞标点", eventType: EventTypeEnum.Marker, + eventCondition: ["Operate:Add", "Id:regex:^(0[1236789]|10)$"], userControl: Debugging)] + public void P3B1_获得二运黑洞标点(Event ev, ScriptAccessory sa) + { + // 取攻击123、锁链123、禁止12 + if (_udmP3Param.当前阶段 != 3200) return; + lock (_pd) + { + if (_udmP3Param.黑洞已出现) return; + + var mark = ev.Id0(); + var tidx = sa.GetPlayerIdIndex((uint)ev.TargetId); + if (tidx < 0) return; + var targetJob = sa.GetPlayerJobByIndex(tidx); + + var pdVal = mark switch + { + 0x01 => 110, // 攻击1 + 0x02 => 120, // 攻击2 + 0x03 => 130, // 攻击3 + 0x06 => 210, // 锁链1 + 0x07 => 220, // 锁链2 + 0x08 => 230, // 锁链3 + 0x09 => 310, // 禁止1 + 0x10 => 320, // 禁止2 + _ => 0 + }; + + _pd.Priorities[tidx] = (tidx + 1) + pdVal; + sa.DebugMsg($"{targetJob} 被标 {mark} == {_pd.Priorities[tidx]}"); + } + } + + [ScriptMethod(name: "P3B1_黑洞轮数增加", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:19512"], userControl: Debugging, suppress: 5000)] + public void P3B1_黑洞轮数增加(Event ev, ScriptAccessory sa) + { + if (!_udmP3Param.黑洞已出现) + { + _udmP3Param.黑洞已出现 = true; + sa.DebugMsg($"头标记录完毕"); + sa.DebugMsg($"{_pd.ShowPriorities()}"); + } + _udmP3Param.黑洞字典.Clear(); + _udmP3Param.黑洞轮数++; + sa.DebugMsg($"当前阶段 {_udmP3Param.当前阶段},清除黑洞字典 {_udmP3Param.黑洞字典.Count}"); + _udmP3Param.黑洞轮数增加完毕.Set(); + } + + [ScriptMethod(name: "P3B1_凯夫卡方位获取", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:19512"], userControl: Debugging, suppress: 5000)] + public void P3B1_凯夫卡方位获取(Event ev, ScriptAccessory sa) + { + _udmP3Param.凯夫卡方位 = _udmP3Param.巨大凯夫卡方位获取(sa, true); + _udmP3Param.凯夫卡方位获取完毕.Set(); + } + + [ScriptMethod(name: "P3B1_外黑洞获取", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:19512"], userControl: Debugging)] + public void P3B1_外黑洞获取(Event ev, ScriptAccessory sa) + { + if (!_udmP3Param.黑洞轮数增加完毕.WaitOne(2000)) return; + if (!_udmP3Param.凯夫卡方位获取完毕.WaitOne(2000)) return; + + lock (_pd) + { + var sid = ev.SourceId; + var obj = sa.GetById(sid); + if (obj == null) return; + var pos = obj.Position; + + // 黑洞距离场中 17 + if (Vector3.Distance(pos, Center) < 16.5f) return; + + // A顺计数 + var region = pos.GetRadian(Center).RadianToRegion(8, 4, isDiagDiv: true, isCw: true) % 8; + var relativeRegion = (region + 8 - _udmP3Param.凯夫卡方位) % 8; + + _udmP3Param.黑洞字典.TryAdd(sid, + (round: _udmP3Param.黑洞轮数, region: region, relativeRegion: relativeRegion, hasTether: false)); + var count = _udmP3Param.黑洞字典.Count; + sa.DebugMsg($"获取到方位 {region} 相对方位 {relativeRegion} 的黑洞 #{count}"); + + if (_udmP3Param.黑洞字典.Count < 3) return; + + // 判断是否有线 + uint tetherId = 0x54; + foreach (var 黑洞 in _udmP3Param.黑洞字典.ToList()) + { + var bc = sa.GetById(黑洞.Key); + if (bc == null) continue; + var valueTuple = _udmP3Param.黑洞字典[黑洞.Key]; + var tetherCount = sa.GetTetherSource((IBattleChara)bc, tetherId).Count; + valueTuple.hasTether = tetherCount > 0; + _udmP3Param.黑洞字典[黑洞.Key] = valueTuple; + sa.DebugMsg( + $"方位 {黑洞.Value.region} 相对方位 {黑洞.Value.relativeRegion} 的黑洞 0x{bc.GameObjectId:X8} {(tetherCount > 0 ? "有" : "没")}线"); + } + + 排序黑洞字典(); + _udmP3Param.外黑洞获取完毕.Set(); + _udmP3Param.黑洞轮数增加完毕.Reset(); + _udmP3Param.凯夫卡方位获取完毕.Reset(); + } + } + + [ScriptMethod(name: "P3B1_接线任务准备_每轮初始", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:19512"], userControl: Debugging, suppress: 5000)] + public void P3B1_接线任务准备_每轮初始(Event ev, ScriptAccessory sa) + { + if (!_udmP3Param.外黑洞获取完毕.WaitOne(2000)) return; + lock (_udmP3Param) + _udmP3Param.黑洞接线任务准备.Reset(); + 清理接线Framework(sa); + _udmP3Param.玩家正在接线 = false; + + // 第一轮黑洞起始,攻击1 + // 第二轮黑洞起始,攻击1,攻击2,攻击3 + // 第三轮黑洞起始,锁链1,锁链2,锁链3 + // 第四轮黑洞起始,禁止1,禁止2 + + var currentTasks = 获取本轮黑洞接线任务(sa, _udmP3Param.黑洞轮数, _udmP3Param.黑洞喷射轮数); + lock (_udmP3Param) + { + _udmP3Param.当前黑洞接线任务 = currentTasks; + _udmP3Param.黑洞接线任务版本++; + _udmP3Param.外黑洞获取完毕.Reset(); + _udmP3Param.黑洞接线任务准备.Set(); + } + } + + [ScriptMethod(name: "P3B1_黑洞接线范围绘图_每轮初始", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:19512"], userControl: true, suppress: 5000)] + public void P3B1_黑洞接线范围绘图_每轮初始(Event ev, ScriptAccessory sa) + { + if (!等待黑洞接线任务(sa, ref _udmP3Param.黑洞接线范围绘图已消费版本, + "P3B1_黑洞接线范围绘图_每轮初始", out var tasks)) return; + foreach (var task in tasks) + 绘制黑洞接线范围(sa, task); + } + + [ScriptMethod(name: "P3B1_黑洞接线TTS_每轮初始", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:19512"], userControl: true, suppress: 5000)] + public void P3B1_黑洞接线TTS_每轮初始(Event ev, ScriptAccessory sa) + { + if (!等待黑洞接线任务(sa, ref _udmP3Param.黑洞接线TTS已消费版本, + "P3B1_黑洞接线TTS_每轮初始", out var tasks)) return; + bool ttsHinted = false; + foreach (var task in tasks) + ttsHinted |= 播报黑洞接线TTS(sa, task.playerIdx, ttsHinted); + } + + [ScriptMethod(name: "P3B1_黑洞接线指路_每轮初始", eventType: EventTypeEnum.AddCombatant, + eventCondition: ["DataId:19512"], userControl: true, suppress: 5000)] + public void P3B1_黑洞接线指路_每轮初始(Event ev, ScriptAccessory sa) + { + if (!等待黑洞接线任务(sa, ref _udmP3Param.黑洞接线指路已消费版本, + "P3B1_黑洞接线指路_每轮初始", out var tasks)) return; + if (_udmP3Param.黑洞喷射轮数 >= 10) return; + foreach (var task in tasks) + 挂载玩家向黑洞方位接线指路(sa, task); + 执行接线Framework(sa, tasks); + } + + [ScriptMethod(name: "P3B1_接线任务准备_中途", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47868", "TargetIndex:1"], userControl: Debugging, suppress: 2500)] + public void P3B1_接线任务准备_中途(Event ev, ScriptAccessory sa) + { + lock (_udmP3Param) + _udmP3Param.黑洞接线任务准备.Reset(); + // 删掉上一轮的连线 + 清理接线Framework(sa); + + sa.Method.RemoveDraw($"绘制黑洞接线 R{_udmP3Param.黑洞轮数} T{_udmP3Param.黑洞喷射轮数}.*"); + _udmP3Param.黑洞喷射轮数++; + if (_udmP3Param.黑洞轮数 is 1 or 4) + { + 翻转黑洞有线状态(); + 排序黑洞字典(); + } + + var currentTasks = 获取本轮黑洞接线任务(sa, _udmP3Param.黑洞轮数, _udmP3Param.黑洞喷射轮数); + lock (_udmP3Param) + { + _udmP3Param.当前黑洞接线任务 = currentTasks; + _udmP3Param.黑洞接线任务版本++; + _udmP3Param.黑洞接线任务准备.Set(); + } + } + + [ScriptMethod(name: "P3B1_黑洞接线范围绘图_中途", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47868", "TargetIndex:1"], userControl: true, suppress: 2500)] + public void P3B1_黑洞接线范围绘图_中途(Event ev, ScriptAccessory sa) + { + if (!等待黑洞接线任务(sa, ref _udmP3Param.黑洞接线范围绘图已消费版本, + "P3B1_黑洞接线范围绘图_中途", out var tasks)) return; + foreach (var task in tasks) + 绘制黑洞接线范围(sa, task); + } + + [ScriptMethod(name: "P3B1_黑洞接线TTS_中途", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47868", "TargetIndex:1"], userControl: true, suppress: 2500)] + public void P3B1_黑洞接线TTS_中途(Event ev, ScriptAccessory sa) + { + if (!等待黑洞接线任务(sa, ref _udmP3Param.黑洞接线TTS已消费版本, + "P3B1_黑洞接线TTS_中途", out var tasks)) return; + bool ttsHinted = false; + foreach (var task in tasks) + ttsHinted |= 播报黑洞接线TTS(sa, task.playerIdx, ttsHinted); + + if (ttsHinted || !_udmP3Param.玩家正在接线) return; + _udmP3Param.玩家正在接线 = false; + sa.Method.TextInfo($"回到人群", 2000); + sa.Method.TTS($"回到人群", 3); + } + + [ScriptMethod(name: "P3B1_黑洞接线指路_中途", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47868", "TargetIndex:1"], userControl: true, suppress: 2500)] + public void P3B1_黑洞接线指路_中途(Event ev, ScriptAccessory sa) + { + if (!等待黑洞接线任务(sa, ref _udmP3Param.黑洞接线指路已消费版本, + "P3B1_黑洞接线指路_中途", out var tasks)) return; + if (_udmP3Param.黑洞喷射轮数 >= 10) return; + foreach (var task in tasks) + 挂载玩家向黑洞方位接线指路(sa, task); + 执行接线Framework(sa, tasks); + } + + private bool 播报黑洞接线TTS(ScriptAccessory sa, int playerIdx, bool ttsHinted) + { + var myIndex = sa.GetMyIndex(); + if (playerIdx == myIndex) + { + if (ttsHinted) return true; + sa.Method.TextInfo($"去接线", 2000); + sa.Method.TTS($"去接线", 3); + _udmP3Param.玩家正在接线 = true; + return true; + } + return false; + } + + private bool 等待黑洞接线任务(ScriptAccessory sa, ref int consumedVersion, string caller, + out List<(int playerIdx, List realRegions, List blackHoles, string name)> tasks) + { + tasks = []; + for (var i = 0; i < 40; i++) + { + if (!_udmP3Param.黑洞接线任务准备.WaitOne(50)) continue; + lock (_udmP3Param) + { + if (_udmP3Param.黑洞接线任务版本 <= consumedVersion) + { + _udmP3Param.黑洞接线任务准备.Reset(); + continue; + } + consumedVersion = _udmP3Param.黑洞接线任务版本; + tasks = _udmP3Param.当前黑洞接线任务.ToList(); + return true; + } + } + sa.DebugMsg($"等待黑洞接线任务超时"); + return false; + } + + private void 翻转黑洞有线状态() + { + foreach (var 黑洞 in _udmP3Param.黑洞字典.ToList()) + { + var valueTuple = 黑洞.Value; + valueTuple.hasTether = !valueTuple.hasTether; + _udmP3Param.黑洞字典[黑洞.Key] = valueTuple; + } + } + + private void 排序黑洞字典() + { + _udmP3Param.黑洞字典 = _udmP3Param.黑洞字典 + .OrderByDescending(kvp => kvp.Value.hasTether) + .ThenBy(kvp => kvp.Value.relativeRegion) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + private List 筛选本轮接线玩家(ScriptAccessory sa, int bhRound, int bhEffectRound) + { + List markFeatureList; + + if (BhStg == BhStgEnum.一人一根线) + markFeatureList = (bhRound, bhEffectRound) switch + { + (1, 0) => [110], + (1, 1) => [110, 120], + (2, 2) => [110, 120, 130], + (2, 3) => [210, 120, 130], + (2, 4) => [210, 220, 130], + (3, 5) => [210, 220, 230], + (3, 6) => [310, 220, 230], + (3, 7) => [310, 320, 230], + (4, 8) => [310, 320], + (4, 9) => [320], + (1, 2) or (2, 5) or (3, 8) or (4, 10) => [], + _ => [] + }; + else + markFeatureList = (bhRound, bhEffectRound) switch + { + (1, 0) => [120], + (1, 1) => [110, 110], + (2, 2) => [110, 120, 130], + (2, 3) => [210, 120, 130], + (2, 4) => [210, 220, 130], + (3, 5) => [210, 220, 230], + (3, 6) => [310, 220, 230], + (3, 7) => [310, 320, 230], + (4, 8) => [320, 320], + (4, 9) => [310], + (1, 2) or (2, 5) or (3, 8) or (4, 10) => [], + _ => [] + }; + + var isKnownNoop = (bhRound, bhEffectRound) is (1, 2) or (2, 5) or (3, 8) or (4, 10); + if (markFeatureList.Count == 0 && !isKnownNoop) + { + sa.DebugMsg($"输入值错误 ({bhRound} {bhEffectRound})"); + } + return markFeatureList; + } + + private KeyValuePair 按黑洞头标特征找玩家(ScriptAccessory sa, int markFeature) + { + var matchedPlayers = _pd.Priorities + .Where(kvp => kvp.Value / 10 * 10 == markFeature) + .OrderBy(kvp => kvp.Value) + .ToList(); + + if (matchedPlayers.Count == 0) + return new KeyValuePair(-1, 0); + + var myIndex = sa.GetMyIndex(); + var myIndexInMatchedPlayersList = matchedPlayers.FindIndex(kvp => kvp.Key == myIndex); + + return myIndexInMatchedPlayersList >= 0 ? matchedPlayers[myIndexInMatchedPlayersList] : matchedPlayers[0]; + } + + private List<(int playerIdx, List realRegions, List blackHoles, string name)> 获取本轮黑洞接线任务(ScriptAccessory sa, int bhRound, int bhEffectRound) + { + var markFeatureList = 筛选本轮接线玩家(sa, bhRound, bhEffectRound); + var tasks = new List<(int playerIdx, List realRegions, List blackHoles, string name)>(); + + for (int i = 0; i < markFeatureList.Count; i++) + { + var markFeature = markFeatureList[i]; + // 黑洞字典已在生成时排好序 + var bh = _udmP3Param.黑洞字典.ElementAt(i); + var blackHoles = new List { bh.Key }; + var realRegions = new List { bh.Value.region }; + + var player = 按黑洞头标特征找玩家(sa, markFeature); + if (player.Key < 0) + { + sa.DebugMsg($"未找到头标特征 {markFeature} 对应玩家"); + continue; + } + + if (i + 1 < markFeatureList.Count && markFeatureList[i + 1] == markFeatureList[i]) + { + var bh2 = _udmP3Param.黑洞字典.ElementAt(i + 1); + blackHoles.Add(bh2.Key); + realRegions.Add(bh2.Value.region); + i++; + } + + var regionStr = string.Join(",", realRegions); + var taskName = $"R{bhRound} T{bhEffectRound} {player.Key} {regionStr}"; + tasks.Add((player.Key, realRegions, blackHoles, taskName)); + sa.DebugMsg( + $"{taskName} | 玩家 {sa.GetPlayerJobByIndex(player.Key)}({player.Key}) | 特征 {markFeature} | 方位 {regionStr} | 黑洞 {string.Join(",", blackHoles.Select(x => x.ToString("X8")))}"); + } + + sa.DebugMsg($"R{bhRound} T{bhEffectRound} 共 {tasks.Count} 个任务"); + return tasks; + } + + private bool 黑洞接线任务属于自己(ScriptAccessory sa, + (int playerIdx, List realRegions, List blackHoles, string name) task) + { + return Debugging || sa.GetMyIndex() == task.playerIdx; + } + + private void 绘制黑洞接线范围(ScriptAccessory sa, + (int playerIdx, List realRegions, List blackHoles, string name) task) + { + if (!黑洞接线任务属于自己(sa, task)) return; + var color = BhConnLineColor.V4; + + for (int i = 0; i < task.blackHoles.Count; i++) + { + var dp = sa.DrawLine(task.blackHoles[i], 0, 0, 7000, $"绘制黑洞接线 {task.name} L{task.realRegions[i]}", + 0, 1, 40, color, byY: true, draw: false); + dp.TargetResolvePattern = PositionResolvePatternEnum.TetherTarget; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Line, dp); + + var dp2 = sa.DrawRect(task.blackHoles[i], 0, 0, 7000, $"绘制黑洞接线 {task.name} R{task.realRegions[i]}", + 0, 6, 40, sa.Data.DefaultDangerColor, draw: false); + dp2.TargetResolvePattern = PositionResolvePatternEnum.TetherTarget; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp2); + } + + sa.DebugMsg($"{sa.GetPlayerJobByIndex(task.playerIdx)} 接真实方位 {string.Join(",", task.realRegions)} 的黑洞"); + } + + private void 挂载玩家向黑洞方位接线指路(ScriptAccessory sa, + (int playerIdx, List realRegions, List blackHoles, string name) task) + { + if (!黑洞接线任务属于自己(sa, task)) return; + var guidePos = task.realRegions.Count == 1 + ? 计算黑洞顺时针侧指路点(task.realRegions[0]) + : 计算两黑洞之间指路点(task.realRegions[0], task.realRegions[1]); + + 挂载接线Framework(sa, guidePos, task); + + sa.DebugMsg($"{sa.GetPlayerJobByIndex(task.playerIdx)} 接真实方位 {string.Join(",", task.realRegions)} 的黑洞"); + } + + private void 挂载接线Framework(ScriptAccessory sa, Vector3 guidePos, + (int playerIdx, List realRegions, List blackHoles, string name) task) + { + // task + // -- playerIdx:需接线的玩家 + // -- realRegions:需接线黑洞所在方位 + // -- blackHoles:需接线黑洞 ObjId + // -- name:任务名称 $"R{bhRound} T{bhEffectRound} {player.Key} {regionStr} + // ---- bhRound:黑洞轮数 + // ---- bhEffectRound:黑洞喷射轮数 + // ---- player.Key:同 playerIdx + // ---- regionStr:需接线黑洞所在方位的集合,即 realRegions 的 Join + + // 绘图指路条件:task.blackHoles 中的黑洞产生的线都被接到 + // 每有一根线,需一个 fwElement 进行控制 + // fwElement + // -- Name:Framework 的 Name,计划用 {player.Key} {blackHoles[i]} + // -- PlayerIndex:需接线 Player 的 Index + // -- BlackHoleId:需接线的黑洞源 + // -- PlayerObjId:需接线 Player 的 ObjectId + // -- FwHandle:用于挂载 sa.Method.RegistFrameworkUpdateAction(Action) + // -- Tethered:若成功接上线则为 true + // -- GuidePos:接到线/接全线后的指路位置 + + // List 中一个 Framework 去控制每个黑洞 + // 外部一个 Framework 检查 List 中的元素是否都是 tethered,然后画指路线 + + for (int i = 0; i < task.blackHoles.Count; i++) + { + ulong playerObjId = sa.Data.PartyList[task.playerIdx]; + bool tethered = false; + + var fwElement = new 接线Framework元素 + { + Name = $"{task.playerIdx} {task.blackHoles[i]}", + TaskName = task.name, + PlayerIndex = task.playerIdx, + BlackHoleId = task.blackHoles[i], + PlayerObjId = playerObjId, + Tethered = tethered, + FwHandle = "", + GuidePos = guidePos + }; + + _udmP3Param.接线FrameworkList.Add(fwElement); + sa.DebugMsg( + $"{fwElement.TaskName} | {sa.GetPlayerJobByIndex(fwElement.PlayerIndex)}({fwElement.PlayerIndex}) 玩家 {fwElement.PlayerObjId:X8} | 黑洞 {fwElement.BlackHoleId:X8} | 真实方位 {task.realRegions[i]} | 元素 {fwElement.Name}"); + } + } + + private void 执行接线Framework(ScriptAccessory sa, + List<(int playerIdx, List realRegions, List blackHoles, string name)> tasks) + { + // 筛选出本轮相关的线元素 + List<接线Framework元素> 当前接线任务Fws = _udmP3Param.接线FrameworkList.ToList(); + if (当前接线任务Fws.Count == 0) return; + sa.DebugMsg( + $"本轮任务 {tasks.Count} 个,接线元素 {当前接线任务Fws.Count} 个:{string.Join(" | ", tasks.Select(task => $"{task.name}:{task.blackHoles.Count}"))}"); + + Dictionary 去站位指路绘制状态 = tasks.ToDictionary(task => task.name, _ => false); + Dictionary 上一次任务接线完成状态 = tasks.ToDictionary(task => task.name, _ => false); + string 去站位指路绘图名(string taskName) => $"绘制黑洞接线 {taskName} G去站位"; + + _udmP3Param.当前接线任务Framework = sa.Method.RegistFrameworkUpdateAction(Action); + void Action() + { + foreach (var fw in 当前接线任务Fws) + { + var bh = sa.GetById(fw.BlackHoleId); + if (bh == null) + { + if (fw.上一次黑洞存在状态) + { + sa.DebugMsg($"{fw.TaskName} | {fw.Name} 黑洞 {fw.BlackHoleId:X8} 当前取不到"); + fw.上一次黑洞存在状态 = false; + } + continue; + } + if (!fw.上一次黑洞存在状态) + { + sa.DebugMsg($"{fw.TaskName} | {fw.Name} 黑洞 {fw.BlackHoleId:X8} 已重新取到"); + fw.上一次黑洞存在状态 = true; + } + var 当前接线目标 = sa.GetTetherSource((IBattleChara)bh, 0x54); + var 当前接线目标列表 = string.Join(",", 当前接线目标.Select(x => x.ToString("X8"))); + if (当前接线目标列表 != fw.上一次接线目标列表) + { + var 当前接线目标Job列表 = string.Join(",", 当前接线目标.Select(x => sa.GetPlayerJobById((uint)x))); + sa.DebugMsg( + $"{fw.TaskName} | {fw.Name} 线目标变化:{(当前接线目标列表 == "" ? "无" : 当前接线目标列表)} | {当前接线目标Job列表}"); + fw.上一次接线目标列表 = 当前接线目标列表; + } + var 刚才已接到线 = fw.Tethered; + var 当前已接到线 = 当前接线目标.Contains(fw.PlayerObjId); + fw.Tethered = 当前已接到线; + + if (当前已接到线) + { + // 如果接到线了,就不需要画去接这根线的指路,清空上一次去接线的相关绘图。 + if (!fw.去接线指路已绘制 && 刚才已接到线) continue; + if (fw.上一次去接线指路绘图名 != "") + sa.Method.RemoveDraw(fw.上一次去接线指路绘图名); + sa.DebugMsg($"{fw.TaskName} | {fw.Name} 接到线了,目标 {fw.PlayerObjId:X8}/{sa.GetPlayerJobByIndex(fw.PlayerIndex)}"); + fw.去接线指路已绘制 = false; + fw.上一次接线目标 = 0; + fw.上一次去接线指路绘图名 = ""; + continue; + } + + if (当前接线目标.Count != 1) + { + // 如果这根线没有处在“明确连到一个玩家”的状态(没有玩家给你连了),则清空“去接线”与“去站位”绘图 + if (去站位指路绘制状态.GetValueOrDefault(fw.TaskName)) + { + sa.Method.RemoveDraw(去站位指路绘图名(fw.TaskName)); + 去站位指路绘制状态[fw.TaskName] = false; + sa.DebugMsg($"{fw.TaskName} | {fw.Name} 线目标数量 {当前接线目标.Count},移除去站位指路"); + } + if (!fw.去接线指路已绘制) continue; + if (fw.上一次去接线指路绘图名 != "") + sa.Method.RemoveDraw(fw.上一次去接线指路绘图名); + sa.DebugMsg($"{fw.TaskName} | {fw.Name} 线目标数量 {当前接线目标.Count},移除去接线指路"); + fw.去接线指路已绘制 = false; + fw.上一次接线目标 = 0; + fw.上一次去接线指路绘图名 = ""; + continue; + } + + // 如果这根线连着错误的玩家 + var 当前接线目标ObjId = 当前接线目标[0]; + if (fw.去接线指路已绘制 && 当前接线目标ObjId == fw.上一次接线目标) continue; + if (去站位指路绘制状态.GetValueOrDefault(fw.TaskName)) + { + sa.Method.RemoveDraw(去站位指路绘图名(fw.TaskName)); + 去站位指路绘制状态[fw.TaskName] = false; + sa.DebugMsg($"{fw.TaskName} | {fw.Name} 线被错误目标 {当前接线目标ObjId:X8}/{sa.GetPlayerJobById((uint)当前接线目标ObjId)} 接到,移除去站位指路"); + } + if (fw.上一次去接线指路绘图名 != "") + sa.Method.RemoveDraw(fw.上一次去接线指路绘图名); + + // 顺利来到这里:当前没接到线,黑洞明确有线连着玩家,这根线连着错误的玩家 + var 当前去接线指路绘图名 = $"{fw.去接线指路绘图名前缀} {当前接线目标ObjId:X8}"; + sa.DebugMsg( + $"{fw.TaskName} | {fw.Name} 快去接线:{sa.GetPlayerJobByIndex(fw.PlayerIndex)}({fw.PlayerObjId:X8}) -> {sa.GetPlayerJobById((uint)当前接线目标ObjId)}({当前接线目标ObjId:X8})"); + sa.DrawGuidance(fw.PlayerObjId, 当前接线目标ObjId, 0, 7000, 当前去接线指路绘图名, sa.Data.DefaultSafeColor); + fw.去接线指路已绘制 = true; + fw.上一次接线目标 = 当前接线目标ObjId; + fw.上一次去接线指路绘图名 = 当前去接线指路绘图名; + } + + foreach (var taskFws in 当前接线任务Fws.GroupBy(fw => fw.TaskName)) + { + // 使用 groupby 将所有线元素进行分组,若一人接两根,会有 R1 T1 5 0,2 这种同名 TaskName 的情况 + var taskName = taskFws.Key; + var allTethered = taskFws.All(fw => fw.Tethered); + if (allTethered != 上一次任务接线完成状态.GetValueOrDefault(taskName)) + { + sa.DebugMsg( + $"{taskName} 接线完成状态变化:{allTethered} | {string.Join(",", taskFws.Select(fw => $"{fw.Name}={fw.Tethered}"))}"); + 上一次任务接线完成状态[taskName] = allTethered; + } + if (!allTethered) + { + if (!去站位指路绘制状态.GetValueOrDefault(taskName)) continue; + sa.Method.RemoveDraw(去站位指路绘图名(taskName)); + 去站位指路绘制状态[taskName] = false; + sa.DebugMsg($"{taskName} 未全接到,移除去站位指路"); + continue; + } + + if (去站位指路绘制状态.GetValueOrDefault(taskName)) continue; + foreach (var fw in taskFws) + sa.Method.RemoveDraw($"绘制黑洞接线 {taskName} G去接线 {fw.Name}.*"); + + var guideFw = taskFws.First(); + sa.DebugMsg($"去站位吧 {taskName} | 玩家 {sa.GetPlayerJobByIndex(guideFw.PlayerIndex)}({guideFw.PlayerObjId:X8}) | GuidePos {guideFw.GuidePos}"); + // $"绘制黑洞接线 {taskName} G去站位"; + sa.DrawGuidance(guideFw.PlayerObjId, guideFw.GuidePos, 0, 7000, 去站位指路绘图名(taskName), sa.Data.DefaultSafeColor); + 去站位指路绘制状态[taskName] = true; + } + } + } + + private void 清理接线Framework(ScriptAccessory sa) + { + foreach (var fw in _udmP3Param.接线FrameworkList) + { + if (fw.FwHandle != "") + sa.Method.UnregistFrameworkUpdateAction(fw.FwHandle); + } + _udmP3Param.接线FrameworkList.Clear(); + + if (_udmP3Param.当前接线任务Framework != "") + sa.Method.UnregistFrameworkUpdateAction(_udmP3Param.当前接线任务Framework); + _udmP3Param.当前接线任务Framework = ""; + } + + private Vector3 计算黑洞顺时针侧指路点(int realRegion) + { + var bhpos = BlackHoleBasePos.RotateAndExtend(Center, realRegion * -45f.DegToRad()); + return bhpos.RotateAndExtend(Center, -45f.DegToRad(), -4.979f); + } + + private Vector3 计算两黑洞之间指路点(int realRegion1, int realRegion2) + { + var bhpos1 = BlackHoleBasePos.RotateAndExtend(Center, realRegion1 * -45f.DegToRad()); + var bhpos2 = BlackHoleBasePos.RotateAndExtend(Center, realRegion2 * -45f.DegToRad()); + var guideDir = bhpos1 + bhpos2 - Center * 2; + + if (guideDir.LengthSquared() < 0.001f) + return 计算黑洞顺时针侧指路点(realRegion1); + + guideDir = Vector3.Normalize(guideDir); + return Center + guideDir * (BlackHoleBasePos.GetLength(Center) - 4.979f); + } + + + #endregion P3B1 黑洞 3200 + + #region P3B2 冰封 3210 + + [ScriptMethod(name: "———————— 《P3B2 冰封》 ————————", eventType: EventTypeEnum.NpcYell, eventCondition: ["HelloayaWorld:asdf"], + userControl: true)] + public void P3B2_分割线(Event ev, ScriptAccessory sa) + { + } + + [ScriptMethod(name: "P3B2_冰封_转阶段", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47887"], userControl: Debugging)] + public void P3B2_冰封_转阶段(Event ev, ScriptAccessory sa) + { + _udmP3Param.当前阶段 = 3210; + sa.DebugMsg($"当前阶段为:P3B2 二运 冰封 {_udmP3Param.当前阶段}"); + _udmP3Param.凯夫卡方位 = _udmP3Param.巨大凯夫卡方位获取(sa, false); + _udmP3Param.冰封北方位 = BlizKefkaIsNorth ? _udmP3Param.凯夫卡方位 : (_udmP3Param.凯夫卡方位 + 4) % 8; + _udmP3Param.西塔方位 = (_udmP3Param.冰封北方位 + 2) % 8; + _udmP3Param.东塔方位 = (_udmP3Param.冰封北方位 - 2 + 8) % 8; + if (P3B1CaptainMode) + sa.Method.MarkClear(); + _udmP3Param.凯夫卡方位获取完毕.Set(); + sa.DebugMsg($"冰封北方位 继承自 凯夫卡方位 {_udmP3Param.凯夫卡方位} C逆"); + } + + [ScriptMethod(name: "P3B2_冰封_引导路径", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47887"], userControl: true)] + public void P3B2_冰封_引导路径一(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.冰封北方位 == -1) + { + if (!_udmP3Param.凯夫卡方位获取完毕.WaitOne(2000)) return; + _udmP3Param.凯夫卡方位获取完毕.Reset(); + } + + if (_udmP3Param.当前阶段 != 3210) return; + _udmP3Param.绘制冰封引导路径 = true; + var myIndex = sa.GetMyIndex(); + + // 盗火策略,第一步在上下;其余,第一步在场中 + var relativeUpDownPos = myIndex <= 3 ? new Vector3(100, 0, 110f) : new Vector3(100, 0, 90f); + var initPos = BlizStg == BlizStgEnum.从上下引导后四角_盗火文档 ? relativeUpDownPos: Center; + initPos = _udmP3Param.基于凯夫卡旋转(initPos, _udmP3Param.冰封北方位); + + // 在这里画一与二 + var targetPos = new Vector3(100, 0, 114.14f); + // 根据位置旋转特定角度 + var rotDeg = myIndex switch + { + 0 or 2 => BlizStg == BlizStgEnum.从场中引导后上下_NoCCHH ? 0f : -45f, + 1 or 3 => BlizStg == BlizStgEnum.从场中引导后上下_NoCCHH ? 0f : 45f, + 4 or 6 => BlizStg == BlizStgEnum.从场中引导后上下_NoCCHH ? 180f : 135f, + 5 or 7 => BlizStg == BlizStgEnum.从场中引导后上下_NoCCHH ? 180f : -135f + }; + targetPos = targetPos.RotateAndExtend(Center, rotDeg.DegToRad(), + BlizStg == BlizStgEnum.从场中引导后上下_NoCCHH ? -3 : 0); + targetPos = _udmP3Param.基于凯夫卡旋转(targetPos, _udmP3Param.冰封北方位); + _udmP3Param.第二枚黄圈引导位置 = targetPos; + + sa.DrawGuidance(initPos, 0, 3000, $"冰封路径一 黄圈 1", sa.Data.DefaultSafeColor); + sa.DrawGuidance(initPos, targetPos, 0, 3000, $"冰封路径一 黄圈 1->2", sa.Data.DefaultDangerColor); + sa.DrawGuidance(targetPos, 3000, 3000, $"冰封路径一 黄圈 2", sa.Data.DefaultSafeColor); + } + + [ScriptMethod(name: "P3B2_冰封_引导路径二", eventType: EventTypeEnum.TargetIcon, + eventCondition: ["Id:00A1"], userControl: Debugging)] + public void P3B2_冰封_引导路径二(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3210) return; + if (!_udmP3Param.绘制冰封引导路径) return; + _udmP3Param.是第一次分摊 = !_udmP3Param.是第一次分摊; + + var targetIdx = sa.GetPlayerIdIndex((uint)ev.TargetId); + _udmP3Param.分摊点TN = targetIdx <= 3; + var myIndex = sa.GetMyIndex(); + + if (_udmP3Param.需要踩塔(myIndex)) + 绘制冰封踩塔路径(sa); + else + 绘制冰封分摊路径(sa); + } + + private int 获得踩塔方位(ScriptAccessory sa) + { + // NoCCHH 踩塔为场基(面向凯夫卡分左右),西塔 MT/H1/D1/D3,东塔 ST/H2/D2/D4 + // 其余踩塔为面基(面向场中分左右),西塔 ST/H2/D1/D3,东塔 MT/H1/D2/D4 + var myIndex = sa.GetMyIndex(); + if (BlizStg == BlizStgEnum.从场中引导后上下_NoCCHH) + return myIndex is 0 or 2 or 4 or 6 ? _udmP3Param.西塔方位 : _udmP3Param.东塔方位; + else + return myIndex is 1 or 3 or 4 or 6 ? _udmP3Param.西塔方位 : _udmP3Param.东塔方位; + } + + private void 绘制冰封踩塔路径(ScriptAccessory sa) + { + Vector3 stackPos = Center; + var towerRegion = 获得踩塔方位(sa); + Vector3 towerPos = new Vector3(100, 0, 110).RotateAndExtend(Center, towerRegion * 45f.DegToRad()); + + // 凯夫卡一定先跺右脚出西塔,如果是踩塔,东塔玩家需要延迟指路,西塔玩家直接去 + bool towerDelay = towerRegion == (BlizKefkaIsNorth ? _udmP3Param.东塔方位 : _udmP3Param.西塔方位); + + if (_udmP3Param.是第一次分摊) + { + sa.DrawGuidance(_udmP3Param.第二枚黄圈引导位置, towerPos, 0, 3000, $"冰封路径二 黄圈 2->塔", sa.Data.DefaultDangerColor); + sa.DrawCircle(towerPos, 3000, towerDelay ? 3500 : 2500, $"冰封路径二 塔范围", 5f, sa.Data.DefaultSafeColor); + sa.DrawGuidance(towerPos, 3000, towerDelay ? 3500 : 2500, $"冰封路径二 黄圈 塔", sa.Data.DefaultSafeColor); + sa.DrawGuidance(towerPos, stackPos, 3000, towerDelay ? 3500 : 2500, $"冰封路径二 踩塔->分摊", sa.Data.DefaultDangerColor); + } + else + { + sa.DrawCircle(towerPos, towerDelay ? 1000 : 0, towerDelay ? 3000 : 4000, + $"冰封路径二 塔范围", 6f, sa.Data.DefaultSafeColor); + sa.DrawGuidance(towerPos, towerDelay ? 1000 : 0, towerDelay ? 3000 : 4000, + $"冰封路径二 塔 第二轮", sa.Data.DefaultSafeColor); + } + + } + + private void 绘制冰封分摊路径(ScriptAccessory sa) + { + Vector3 stackPos = Center; + var towerRegion = 获得踩塔方位(sa); + Vector3 towerPos = new Vector3(100, 0, 110).RotateAndExtend(Center, towerRegion * 45f.DegToRad()); + bool towerDelay = towerRegion == (BlizKefkaIsNorth ? _udmP3Param.东塔方位 : _udmP3Param.西塔方位); + + if (_udmP3Param.是第一次分摊) + { + sa.DrawGuidance(_udmP3Param.第二枚黄圈引导位置, stackPos, 0, 3000, $"冰封路径二 黄圈 2->分摊", sa.Data.DefaultDangerColor); + sa.DrawGuidance(stackPos, 3000, towerDelay ? 3500 : 2500, $"冰封路径二 黄圈 分摊", sa.Data.DefaultSafeColor); + sa.DrawGuidance(stackPos, towerPos, 3000, towerDelay ? 3500 : 2500, $"冰封路径二 分摊->踩塔", sa.Data.DefaultDangerColor); + } + else + { + sa.DrawGuidance(stackPos, towerDelay ? 1000 : 0, towerDelay ? 3000 : 4000, $"冰封路径二 分摊 第二轮", sa.Data.DefaultSafeColor); + } + } + + [ScriptMethod(name: "P3B2_轰击/顶起", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47875", "TargetIndex:1"], userControl: true)] + public void P3B2_轰击旋风(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3210) return; + if (Vector3.Distance(_udmP3Param.第一次轰击位置, ev.TargetPosition) > 80) + _udmP3Param.第一次轰击位置 = ev.TargetPosition; + else + { + sa.DrawCircle(ev.TargetPosition, 0, 15000, $"轰击旋风1", 6, sa.Data.DefaultDangerColor.WithW(2f)); + sa.DrawCircle(_udmP3Param.第一次轰击位置, 0, 15000, $"轰击旋风2", 6, sa.Data.DefaultDangerColor.WithW(2f)); + + // 顺便恢复透明度 + var obj1 = sa.GetById(_udmP3Param.ObjectId_卡奥斯); + sa.AlphaModify(obj1, 1f, currentAlpha => currentAlpha <= 0.8f); + var obj2 = sa.GetById(_udmP3Param.ObjectId_艾克斯迪司); + sa.AlphaModify(obj2, 1f, currentAlpha => currentAlpha <= 0.8f); + } + + } + + [ScriptMethod(name: "P3B2_顶起删除轰击旋风", eventType: EventTypeEnum.ActionEffect, + eventCondition: ["ActionId:47878", "TargetIndex:1"], userControl: Debugging)] + public void P3B2_顶起删除轰击旋风(Event ev, ScriptAccessory sa) + { + sa.Method.RemoveDraw($"轰击旋风.*"); + _udmP3Param.当前阶段 = 3300; + } + + [ScriptMethod(name: "P3B2_冰封移动提示", eventType: EventTypeEnum.StartCasting, + eventCondition: ["ActionId:47889"], userControl: true)] + public async void P3B2_冰封移动提示(Event ev, ScriptAccessory sa) + { + if (_udmP3Param.当前阶段 != 3210) return; + + var runId = _runId; + await Task.Delay(1000); + if (runId != _runId) return; + if (_udmP3Param.当前阶段 != 3210) return; + + sa.Method.TextInfo($"远离场中 动动动", 3000); + sa.Method.TTS($"远离场中 动动动", 3); + } + + #endregion P3B2 冰封 3210 +} + + +#region 优先级字典 +internal class PriorityDict +{ + // ReSharper disable once NullableWarningSuppressionIsUsed + public Dictionary Priorities {get; set;} = null!; + public string Annotation { get; set; } = ""; + public int ActionCount { get; set; } = 0; + + private static readonly List Role = ["MT", "ST", "H1", "H2", "D1", "D2", "D3", "D4"]; + + public void Init(ScriptAccessory sa, string annotation, int partyNum = 8, bool refreshActionCount = true) + { + 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; + } + + public string ShowGroup(string name, List> kvpList) + { + return $"{name}:{string.Join(" ", kvpList.Select(x => $"({Role[x.Key]}, {x.Value})"))}"; + } +} + +#endregion 优先级字典 类 + + +#region 参数容器类 +internal class UDMP3Params +{ + public bool Debugging = true; + public int 当前阶段 = 0; + public ulong ObjectId_卡奥斯 = 0; + public ulong ObjectId_艾克斯迪司 = 0; + public bool 拉艾克斯迪司的是MT = false; + public string 判断防火墙目标Framework = ""; + public int 上一次防火墙状态 = 0; + public bool 防火墙已开启 = false; + public bool 经纬聚爆先打左右 = false; + public bool 绘制经纬聚爆范围 = false; + public bool 经纬聚爆第二段绘制完毕 = false; + + // ---- 一运 ---- + public bool 是长火 = false; + public int 一运状态记录次数 = 0; + public int 火水晶方位 = -1; + public int 水水晶方位 = -1; + public int 风水晶方位 = -1; + public int 无水晶方位 = -1; + + public int 究极冲击波起始方位 = -1; + public bool 究极冲击波为顺时针 = false; + public bool 究极冲击波记录完毕 = false; + + public List> 火组 = new(); + public List> 水组 = new(); + public List> 风组 = new(); + public ManualResetEvent 一运状态记录 = new(false); + public ManualResetEvent 一运水晶记录 = new(false); + public ManualResetEvent 一水火准备 = new(false); + + public int 一水火指路与绘图时间 = 10000; + public int 二水火指路与绘图时间 = 9000; + public int 真空波指路时间 = 7000; + public string 真空波面向辅助Framework = ""; + public DateTime 真空波面向辅助触发时间 = DateTime.MinValue; + + // ---- 二运 ---- + public int 二运状态记录次数 = 0; + public ManualResetEvent 二运状态记录 = new(false); + public int 黑洞轮数 = 0; + public int 黑洞喷射轮数 = 0; + public bool 玩家正在接线 = false; + public Dictionary 黑洞字典 = new(); + public ManualResetEvent 外黑洞获取完毕 = new(false); + public ManualResetEvent 黑洞轮数增加完毕 = new(false); + public ManualResetEvent 凯夫卡方位获取完毕 = new(false); + public int 凯夫卡方位 = 0; + + public int 冰封北方位 = -1; + public int 西塔方位 = -1; + public int 东塔方位 = -1; + public bool 分摊点TN = false; + public bool 绘制冰封引导路径 = false; + public bool 是第一次分摊 = false; + public Vector3 第二枚黄圈引导位置 = new Vector3(100, 0, 100); + public Vector3 第一次轰击位置 = new Vector3(0, 0, 0); + + public bool 获得过第一个黑洞状态 = false; + public bool 黑洞已出现 = false; + public bool 黑洞三麻取反 = false; + public bool 混沌泥土最末 = false; + + public List<(int playerIdx, List realRegions, List blackHoles, string name)> 当前黑洞接线任务 = []; + public ManualResetEvent 黑洞接线任务准备 = new(false); + public int 黑洞接线任务版本 = 0; + public int 黑洞接线范围绘图已消费版本 = 0; + public int 黑洞接线TTS已消费版本 = 0; + public int 黑洞接线指路已消费版本 = 0; + public List<接线Framework元素> 接线FrameworkList = []; + public string 当前接线任务Framework = ""; + + public void Reset(ScriptAccessory sa) + { + 当前阶段 = 0; + + // 一运 + 是长火 = false; + 一运状态记录次数 = 0; + 火水晶方位 = -1; + 水水晶方位 = -1; + 风水晶方位 = -1; + 无水晶方位 = -1; + 拉艾克斯迪司的是MT = false; + sa.Method.UnregistFrameworkUpdateAction(判断防火墙目标Framework); + 判断防火墙目标Framework = ""; + 上一次防火墙状态 = 0; + 防火墙已开启 = false; + 经纬聚爆先打左右 = false; + 绘制经纬聚爆范围 = false; + 经纬聚爆第二段绘制完毕 = false; + + 究极冲击波起始方位 = -1; + 究极冲击波为顺时针 = false; + 究极冲击波记录完毕 = false; + + ObjectId_卡奥斯 = 0; + ObjectId_艾克斯迪司 = 0; + + 一运状态记录 = new(false); + 一运水晶记录 = new(false); + 一水火准备 = new(false); + sa.Method.UnregistFrameworkUpdateAction(真空波面向辅助Framework); + 真空波面向辅助Framework = ""; + 真空波面向辅助触发时间 = DateTime.MinValue; + + 火组 = new(); + 水组 = new(); + 风组 = new(); + + 一水火指路与绘图时间 = 10000; + 二水火指路与绘图时间 = 9000; + 真空波指路时间 = 7000; + + // 二运 + 二运状态记录次数 = 0; + 二运状态记录 = new(false); + 黑洞轮数 = 0; + 黑洞喷射轮数 = 0; + 玩家正在接线 = false; + 黑洞字典 = new Dictionary(); + 外黑洞获取完毕 = new(false); + 黑洞轮数增加完毕 = new(false); + 凯夫卡方位获取完毕 = new(false); + 凯夫卡方位 = 0; + + 冰封北方位 = -1; + 西塔方位 = -1; + 东塔方位 = -1; + 分摊点TN = false; + 绘制冰封引导路径 = false; + 是第一次分摊 = false; + 第二枚黄圈引导位置 = new Vector3(100, 0, 100); + 第一次轰击位置 = new Vector3(0, 0, 0); + + 获得过第一个黑洞状态 = false; + 黑洞已出现 = false; + 黑洞三麻取反 = false; + 混沌泥土最末 = false; + + 当前黑洞接线任务 = []; + 黑洞接线任务准备 = new(false); + 黑洞接线任务版本 = 0; + 黑洞接线范围绘图已消费版本 = 0; + 黑洞接线TTS已消费版本 = 0; + 黑洞接线指路已消费版本 = 0; + foreach (var fw in 接线FrameworkList) + { + if (fw.FwHandle != "") + sa.Method.UnregistFrameworkUpdateAction(fw.FwHandle); + } + 接线FrameworkList.Clear(); + if (当前接线任务Framework != "") + sa.Method.UnregistFrameworkUpdateAction(当前接线任务Framework); + 当前接线任务Framework = ""; + + sa.DebugMsg($"绝妖星乱舞 P3 参数重置"); + } +} + +internal static class P3AExtension +{ + public static void 打印水晶与分组(this UDMP3Params prm, ScriptAccessory sa, PriorityDict pd) + { + sa.DebugMsg($"是长 {(prm.是长火 ? "火" : "水")}"); + sa.DebugMsg($"火 {prm.火水晶方位} 水 {prm.水水晶方位} 风 {prm.风水晶方位} 无 {prm.无水晶方位}"); + sa.DebugMsg(pd.ShowGroup("风组", prm.风组)); + sa.DebugMsg(pd.ShowGroup("水组", prm.水组)); + sa.DebugMsg(pd.ShowGroup("火组", prm.火组)); + } + + public static bool 一运状态记录完毕(this UDMP3Params prm) => + prm.一运状态记录次数 == 12; + + public static bool 水晶状态记录完毕(this UDMP3Params prm) => + prm is { 火水晶方位: >= 0, 水水晶方位: >= 0, 风水晶方位: >= 0 }; + + public static void 成员分组(this UDMP3Params prm, ScriptAccessory sa, PriorityDict pd) + { + prm.火组.Clear(); + prm.水组.Clear(); + prm.风组.Clear(); + + prm.风组.Add(pd.SelectSpecificPriorityIndex(0)); // 顺风 + prm.风组.Add(pd.SelectSpecificPriorityIndex(1)); // 逆风 + prm.风组.Add(pd.SelectSpecificPriorityIndex(2)); // 顺风 + prm.风组.Add(pd.SelectSpecificPriorityIndex(3)); // 逆风 + + prm.火组.Add(pd.SelectSpecificPriorityIndex(4)); + prm.火组.Add(pd.SelectSpecificPriorityIndex(5)); + prm.水组.Add(pd.SelectSpecificPriorityIndex(6)); + prm.水组.Add(pd.SelectSpecificPriorityIndex(7)); + + // 再按 KVP.value 的个位数大小进行 sort,小的在前 + prm.风组.Sort((a, b) => a.Value.GetDecimalDigit(0).CompareTo(b.Value.GetDecimalDigit(0))); + prm.火组.Sort((a, b) => a.Value.GetDecimalDigit(0).CompareTo(b.Value.GetDecimalDigit(0))); + prm.水组.Sort((a, b) => a.Value.GetDecimalDigit(0).CompareTo(b.Value.GetDecimalDigit(0))); + } + + public static bool 当前轮为火(this UDMP3Params prm) + // 第一轮 XOR 是长火 + => prm.当前阶段 == 3100 ^ prm.是长火; + + public static bool 需要背对(this UDMP3Params prm, int pdValue) + // 混沌之风,背对击退距离减半 + => pdValue.GetDecimalDigit(1) == 1; + + public static Vector3 基于水晶旋转(this UDMP3Params prm, Vector3 basePos, int crystalRegion) + { + var center = new Vector3(100, 0, 100); + return basePos.RotateAndExtend(center, (crystalRegion * 90f).DegToRad()); + } + + public static int 查询组序(this UDMP3Params prm, List> group, int myIndex) + => group.FindIndex(kvp => kvp.Key == myIndex); + + public static bool 水组有近战(this UDMP3Params prm, ScriptAccessory sa) + { + bool result = false; + foreach (var kvp in prm.水组) + result |= kvp.Key % 4 <= 1; + sa.DebugMsg($"水组 {(result ? "有" : "无")} 近战"); + return result; + } +} + +internal static class P3BExtension +{ + public static bool 二运状态记录完毕(this UDMP3Params prm) => + prm.二运状态记录次数 == 10; + + public static int 巨大凯夫卡方位获取(this UDMP3Params prm, ScriptAccessory sa, bool cwFromA = true) + { + var obj = sa.GetByDataId(19504u).FirstOrDefault(); + if (obj == null) + { + sa.DebugMsg($"获取失败"); + return 0; + } + + // ? A顺计数 : C逆计数 + var region = cwFromA + ? (obj.Rotation.RadianToRegion(8, 4, isDiagDiv: true, isCw: true) + 4) % 8 + : (obj.Rotation.RadianToRegion(8, isDiagDiv: true) + 4) % 8; + + // prm.凯夫卡方位 = region; + sa.DebugMsg($"凯夫卡方位 {region} {(cwFromA ? "A顺" : "C逆")}"); + return region; + } + + public static (int attackRegion, int safeRegion) 巨大凯夫卡半场刀方向(this UDMP3Params prm, ScriptAccessory sa, bool isRightHand) + { + // A顺, + var bossRegion = prm.凯夫卡方位; + var attackRegion = (bossRegion + 8 + (isRightHand ? -2 : 2)) % 8; + var safeRegion = (bossRegion + 8 + (isRightHand ? 2 : -2)) % 8; + sa.DebugMsg($"攻击 {attackRegion} 安全 {safeRegion} A顺"); + return (attackRegion, safeRegion); + } + + public static Vector3 基于凯夫卡旋转(this UDMP3Params prm, Vector3 basePos, int bossRegion) + { + var center = new Vector3(100, 0, 100); + return basePos.RotateAndExtend(center, (bossRegion * 45f).DegToRad()); + } + + public static bool 需要踩塔(this UDMP3Params prm, int myIndex) + { + // 分摊TN 我是TN 我分摊 false + // 分摊TN 我是D 我踩塔 true + // 分摊D 我是TN 我踩塔 true + // 分摊D 我是TN 我分摊 false + return prm.分摊点TN ^ (myIndex <= 3); + } +} + +internal class 接线Framework元素 +{ + public string Name { get; init; } = ""; + public string TaskName { get; init; } = ""; + public int PlayerIndex { get; init; } + public string FwHandle { get; set; } = ""; + public bool Tethered { get; set; } = false; + public ulong BlackHoleId { get; init; } + public ulong PlayerObjId { get; init; } + public Vector3 GuidePos { get; init; } + public bool 去接线指路已绘制 { get; set; } = false; + public ulong 上一次接线目标 { get; set; } = 0; + public string 上一次去接线指路绘图名 { get; set; } = ""; + public string 上一次接线目标列表 { get; set; } = ""; + public bool 上一次黑洞存在状态 { get; set; } = true; + public string 去接线指路绘图名前缀 => $"绘制黑洞接线 {TaskName} G去接线 {Name}"; +} + +#endregion 参数容器类 + +#region 函数集 +internal 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 bool TryParseVfxHandle(this Event ev, out nint handleId) + { + handleId = 0; + var rawHandle = ev["Handle"]; + if (string.IsNullOrWhiteSpace(rawHandle)) + return false; + + var handleText = rawHandle.Trim(); + if (handleText.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + handleText = handleText[2..]; + + return nint.TryParse(handleText, System.Globalization.NumberStyles.HexNumber, null, out handleId); + } + + public static uint Id0(this Event ev) + { + return ParseHexId(ev["Id"], out var id) ? id : 0; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static uint DataId(this Event ev) + { + return JsonConvert.DeserializeObject(ev["DataId"]); + } +} + +internal static class IbcHelper +{ + public static IGameObject? GetById(this ScriptAccessory sa, ulong gameObjectId) + { + return sa.Data.Objects.SearchById(gameObjectId); + } + public static IEnumerable GetByDataId(this ScriptAccessory sa, uint dataId) + { + return sa.Data.Objects.Where(x => x.DataId == dataId); + } + public static List GetTetherSource(this ScriptAccessory sa, IBattleChara? battleChara, uint tetherId) + { + List tetherSourceId = []; + if (battleChara == null || !battleChara.IsValid()) return []; + unsafe + { + BattleChara* chara = (BattleChara*)battleChara.Address; + var tetherList = chara->Vfx.Tethers; + + foreach (var tether in tetherList) + { + if (tether.Id != tetherId) continue; + tetherSourceId.Add(tether.TargetId.ObjectId); + } + } + return tetherSourceId; + } +} +#region 计算函数 + +internal 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 = 0) + { + 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); + + /// + /// 获取给定整数的指定位数 + /// + /// 给定数值 + /// 对应位数,个位为0 + /// 返回指定位的数字,如果x超出范围返回0 + public static int GetDecimalDigit(this int val, int x) + => (int)(Math.Abs(val) / Math.Pow(10, x) % 10); + + /// + /// 获取整数的指定二进制位值 + /// + /// 给定数值 + /// 二进制位位置,从最低位开始,最低位为0 + /// 返回指定位的值:0 或 1 + public static int GetBinaryBit(this int val, int bitPosition) + => (val >> bitPosition) & 1; + + /// + /// 获得两个弧度(rad到radReference)的差值,逆时针增加大于0 + /// + /// 取值角度 + /// 参考角度 + /// + public static float GetDiffRad(this float rad, float radReference) + { + var diff = (rad - radReference + 4 * float.Pi) % (2 * float.Pi); + if (diff > float.Pi) diff -= 2 * float.Pi; + return diff; + } +} + +#endregion 计算函数 + +#region 位置序列函数 +internal 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]; + } + +} +#endregion 位置序列函数 + +#region 绘图函数 + +internal 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, Vector4 color, + 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 = color; + 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, + Vector4 color, float rotation = 0, float width = 1f, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, color, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, Vector4 color, float rotation = 0, float width = 1f, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, color, rotation, width, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, Vector4 color, 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, color, byTime,false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, Vector4 color, 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, color, 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, Vector4 color, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, innerScale == 0 ? DrawTypeEnum.Fan : DrawTypeEnum.Donut, color, 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, Vector4 color, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, color, 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, Vector4 color, 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, color, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, Vector4 color, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, color, byTime, byY, draw); + + /// + /// 返回线型绘图 + /// + /// + /// 线条起始 + /// 线条目标 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 旋转角度 + /// 线条宽度 + /// 线条长度 + /// 是否随时间扩充 + /// 是否随距离扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawLine(this ScriptAccessory sa, + object ownerObj, object targetObj, int delay, int destroy, string name, float rotation, + float width, float length, Vector4 color, 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, color, byTime, byY, draw); + + /// + /// 返回两对象间连线绘图 + /// + /// + /// 起始源 + /// 目标源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 线宽 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawConnection(this ScriptAccessory sa, object ownerObj, object targetObj, + int delay, int destroy, string name, Vector4 color, float width = 1f, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, 0, width, width, + 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Line, color, 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; + } +} + +#endregion 绘图函数 + +#region 标点函数 + +internal static class MarkerHelper +{ + 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 int GetMarkedPlayerIndex(this ScriptAccessory sa, List markerList, MarkType marker) + { + return markerList.IndexOf(marker); + } +} + +#endregion + +#region 调试函数 + +internal static class DebugFunction +{ + private static readonly List PrefixWhiteList = [""]; + private static readonly List PrefixBlackList = [""]; + public static void DebugMsg(this ScriptAccessory sa, string msg, + [CallerMemberName] string prefix = "", + bool showInChatBox = true, bool enableWhiteList = false, bool enableBlackList = true) + { + if (!UDM_P3.Debugging) return; + + if (enableWhiteList) + if (!PrefixWhiteList.Contains(prefix)) return; + if (enableBlackList) + if (PrefixBlackList.Contains(prefix)) return; + + sa.Log.Debug($"[{prefix}] {msg}"); + if (!showInChatBox) return; + sa.Method.SendChat($"/e [{prefix}] {msg}"); + } +} + +#endregion 调试函数 + +#region 特殊函数 + +internal static class SpecialFunction +{ + 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.GameObjectId} => {radian.RadToDeg()}"); + + if (!show) return; + var ownerObj = sa.GetById(obj.GameObjectId); + if (ownerObj == null) return; + var dp = sa.DrawGuidance(obj.GameObjectId, 0, 0, 2000, $"改变面向 {obj.Name.TextValue}", sa.Data.DefaultSafeColor, radian, draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Arrow, dp); + } + + public static unsafe bool IsMoving(this ScriptAccessory sa) + { + FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentMap* ptr = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentMap.Instance(); + return ptr is not null && ptr->IsPlayerMoving; + } + + public static unsafe void WriteVisible(this ScriptAccessory sa, IGameObject? actor, bool visible) + { + const VisibilityFlags VISIBLE_FLAG = VisibilityFlags.None; + const VisibilityFlags INVISIBILITY_FLAG = VisibilityFlags.Model; + try + { + var flagsPtr = &((GameObject*)actor?.Address)->RenderFlags; + *flagsPtr = visible ? VISIBLE_FLAG : INVISIBILITY_FLAG; + } + catch (Exception e) + { + sa.Log.Error(e.ToString()); + throw; + } + } + + 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 AlphaModify(this ScriptAccessory sa, IGameObject? obj, float alpha, + Func? shouldModify = null) + { + alpha = Math.Clamp(alpha, 0f, 1f); + sa.Method.RunOnMainThreadAsync(Action); + void Action() + { + if (obj == null) return; + + Character* charaStruct = (Character*)obj.Address; + if (!obj.IsValid() || !charaStruct->IsReadyToDraw()) + { + sa.Log.Error($"传入的IGameObject不合法。"); + return; + } + + if (!charaStruct->IsCharacter()) + { + sa.Log.Error($"传入的IGameObject不是Character,无法修改透明度。"); + return; + } + + if (shouldModify != null && !shouldModify(charaStruct -> Alpha)) + return; + + charaStruct->Alpha = alpha; + // charaStruct->DisableDraw(); + // charaStruct->EnableDraw(); + + // sa.Log.Debug($"AlphaModify => {obj.Name.TextValue} | {obj} => {alpha}"); + } + } +} + +#endregion 特殊函数 + +#endregion 函数集 diff --git a/lianying1997/07-Dawntrail/UnrealShinryu/UnrealShinryu.cs b/lianying1997/07-Dawntrail/UnrealShinryu/UnrealShinryu.cs new file mode 100644 index 0000000..14ffa1e --- /dev/null +++ b/lianying1997/07-Dawntrail/UnrealShinryu/UnrealShinryu.cs @@ -0,0 +1,800 @@ +using System; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent.Struct; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Module.Draw.Manager; +using KodakkuAssist.Module.GameEvent.Types; +using KodakkuAssist.Extensions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Numerics; +using Newtonsoft.Json; +using System.Linq; +using System.ComponentModel; +using Dalamud.Utility.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Vfx; +using Lumina.Excel.Sheets; + +namespace KodakkuScripts.UsamisKodakku._07_Dawntrail.UnrealShinryu; + +[ScriptType(name: Name, territorys: [730, 1372], guid: "fcf45bf5-bb72-42f8-b918-4c4b779fb70c", + version: Version, author: "Usami", note: NoteStr, updateInfo: UpdateInfo)] + +public class UnrealShinryu +{ + const string NoteStr = + $""" + {Version} + 初版 + """; + + const string UpdateInfo = + $""" + {Version} + 1. 增加 P1 超新星站水坑安全区提示 + 2. 增加 P1 螺旋冲锋危险区提示(怎么压不掉?) + """; + + private const string Name = "Shinryu-Ur [神龙幻巧战]"; + private const string Version = "0.0.0.3"; + 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 Center1 = new Vector3(0, -380, 0); + + private long 机制上次触发时间; + + private ShinryuParams _shinryuParam = new ShinryuParams(); + + public void Init(ScriptAccessory sa) + { + sa.Log.Debug($"[DEBUG] 脚本 {Name} v{Version}{DebugVersion} 完成初始化."); + sa.Method.RemoveDraw(".*"); + _shinryuParam.Reset(sa); + } + + // 攻击(左翼/右翼) 9720 50260 + [ScriptMethod(name: "判断副本", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^(9720|50260)$"], + userControl: Debugging)] + public void 判断副本(Event ev, ScriptAccessory sa) + { + if (_shinryuParam.副本判断完毕) return; + _shinryuParam.是幻巧战 = ev.ActionId != 9720; + _shinryuParam.副本判断完毕 = true; + } + + [ScriptMethod(name: "判断大地之怒", eventType: EventTypeEnum.ObjectEffect, eventCondition: ["Id1:4", "Id2:8"], + userControl: Debugging)] + public void 判断大地之怒(Event ev, ScriptAccessory sa) + { + var distance = (ev.SourcePosition - Center1).Length(); + if (distance > 2f) return; + _shinryuParam.绿色地板裂纹 = true; + sa.DebugMsg($"[INFO]【大地之怒】绿色地板裂纹 True"); + } + + [ScriptMethod(name: "火焰链", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0061)$"], + userControl: true)] + public void 火焰链(Event ev, ScriptAccessory sa) + { + lock (_shinryuParam) + { + if (MathTools.Debounce(ref 机制上次触发时间, 500)) + { + _shinryuParam.火焰链次数++; + sa.DebugMsg($"[INFO]【火焰链】当前次数:{_shinryuParam.火焰链次数}"); + }; + } + + if (ev.TargetId != sa.Data.Me) return; + if (_shinryuParam.火焰链次数 is 3 or 4) return; + + // 04, 15, 26, 37 + var myIndex = sa.GetMyIndex(); + var dir = (myIndex % 4) switch + { + 0 => "左上", + 1 => "右上", + 2 => _shinryuParam.火焰链次数 == 2 ? "左上" : "左下", + 3 => _shinryuParam.火焰链次数 == 2 ? "右上" : "右下", + _ => "" + }; + sa.Method.TextInfo($"即将在 {dir} 拉断锁链", 2500); + sa.Method.TTS($"即将在 {dir} 拉断锁链"); + } + + // 巨浪 9690 50230 + [ScriptMethod(name: "巨浪击退", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9690|50230)$"], + userControl: true)] + public void 巨浪击退(Event ev, ScriptAccessory sa) + { + var region = ev.SourcePosition.GetRadian(Center1).RadianToRegion(4, 0, true); + DrawRegionKnockback(sa, region , "巨浪击退"); + sa.DebugMsg($"[INFO]【巨浪击退】源于方位 {region}"); + + void DrawRegionKnockback(ScriptAccessory sa, int dir, string name) + { + var dp = sa.DrawLine(sa.Data.Me, 0, 4000, 6000, name, dir * 90f.DegToRad() + 180f.DegToRad(), 3f, 35f, + sa.Data.DefaultDangerColor.WithW(2f), draw: false); + dp.FixRotation = true; + sa.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + } + + // 冰柱突刺 9712 50252 + [ScriptMethod(name: "冰柱突刺直线", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9712|50252)$"], + userControl: true)] + public void 冰柱突刺直线(Event ev, ScriptAccessory sa) + { + sa.DrawRect(ev.SourceId, 0, 2500, $"冰柱突刺", 0, 10, 60, sa.Data.DefaultDangerColor.WithW(2f)); + } + + [ScriptMethod(name: "放尾巴", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(007E)$"], + userControl: true)] + public void 放尾巴(Event ev, ScriptAccessory sa) + { + _shinryuParam.放尾巴次数++; + sa.DebugMsg($"[INFO]【放尾巴】当前次数:{_shinryuParam.放尾巴次数}"); + + if (ev.TargetId != sa.Data.Me) return; + var pos = _shinryuParam.放尾巴次数 switch + { + 1 => "右下", + 2 => "左下", + 3 => _shinryuParam.绿色地板裂纹 ? "左上" : "中间", + _ => "" + }; + sa.Method.TextInfo($"{pos} 放尾巴", 2500); + sa.Method.TTS($"{pos} 放尾巴"); + } + + // 尾部猛击 9698 50238 + [ScriptMethod(name: "尾部猛击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9698|50238)$"], + userControl: true)] + public void 尾部猛击(Event ev, ScriptAccessory sa) + { + sa.DrawRect(ev.SourcePosition, 0, 3000, $"尾部猛击", ev.SourceRotation, 20, 20, sa.Data.DefaultDangerColor.WithW(2f)); + sa.DrawRect(ev.SourcePosition, 0, 3000, $"尾部猛击", ev.SourceRotation + 180f.DegToRad(), 20, 20, sa.Data.DefaultDangerColor.WithW(2f)); + } + + // 闪电 9706 50246 + [ScriptMethod(name: "闪电", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9706|50246)$"], + userControl: true)] + public void 闪电(Event ev, ScriptAccessory sa) + { + // 找到水坑 + var sinks = sa.GetByDataId(2004237); + foreach (var sink in sinks) + sa.DrawCircle(sink!.GameObjectId, 0, 8000, $"水坑", 5.25f, new Vector4(1, 0, 0, 5)); + + // 玩家间分散 + foreach (var p in sa.Data.PartyList.Where(p => p != sa.Data.Me)) + sa.DrawCircle(p, 0, 8000, $"闪电分散", 5, sa.Data.DefaultDangerColor.WithW(2f), byTime: true); + } + + // 吹雪 9713 50253 + [ScriptMethod(name: "吹雪 AOE 提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9713|50253)$"], + userControl: true)] + public void 吹雪(Event ev, ScriptAccessory sa) + { + sa.Method.TextInfo($"AOE", 2500, true); + sa.Method.TTS($"AOE"); + } + + // 死亡轮回 9715 50255 + [ScriptMethod(name: "死亡轮回", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9715|50255)$"], + userControl: true)] + public void 死亡轮回(Event ev, ScriptAccessory sa) + { + _shinryuParam.死亡轮回次数++; + sa.DebugMsg($"[INFO]【死亡轮回】次数:{_shinryuParam.死亡轮回次数}"); + + var myIndex = sa.GetMyIndex(); + var hintText = myIndex switch + { + 0 or 1 => "分摊死刑", + _ => "即将躲避 天光轮回" + }; + sa.Method.TextInfo(hintText, 2500, true); + sa.Method.TTS(hintText); + + var isTank = sa.Data.MyObject!.IsTank(); + sa.DrawCircle(ev.TargetId, 0, 4000, $"死亡轮回目标", 4f, + (isTank ? sa.Data.DefaultSafeColor : sa.Data.DefaultDangerColor).WithW(2f), byTime: true); + } + + // 制裁之雷 9723 50263 + [ScriptMethod(name: "制裁之雷", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9723|50263)$"], + userControl: true)] + public void 制裁之雷(Event ev, ScriptAccessory sa) + { + var sinks = sa.GetByDataId(2004237); + foreach (var sink in sinks) + sa.DrawCircle(sink!.GameObjectId, 0, 8000, $"水坑", 5.25f, new Vector4(1, 0, 0, 5)); + } + + // 地狱之火炎 9722 50262 + // 超新星 9704 50244 + [ScriptMethod(name: "地狱之火炎与超新星", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9722|50262)$"], + userControl: true)] + public void 地狱之火炎与超新星(Event ev, ScriptAccessory sa) + { + var sinks = sa.GetByDataId(2004237); + foreach (var sink in sinks) + sa.DrawCircle(sink!.GameObjectId, 0, 8000, $"水坑", 5.25f, sa.Data.DefaultSafeColor.WithW(5f)); + } + + // 极神龙 DataId 8026 19934 + [ScriptMethod(name: "大地吐息", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^(0028)$"], + userControl: true)] + public void 大地吐息(Event ev, ScriptAccessory sa) + { + var bossObj = sa.GetByDataId(_shinryuParam.是幻巧战 ? 19934u : 8026u).First(); + sa.DrawFan(bossObj!.GameObjectId, ev.TargetId, 0, 6000, $"大地吐息扇形", 60f.DegToRad(), 0f, 80f, 0f, sa.Data.DefaultDangerColor.WithW(2f)); + if (ev.TargetId != sa.Data.Me) return; + var myIndex = sa.GetMyIndex(); + + var pos = myIndex switch + { + <= 3 => "左上", + _ => "右上" + }; + sa.Method.TextInfo($"{pos} 引导扇形", 2500); + sa.Method.TTS($"{pos} 引导扇形"); + } + + // 钻石星辰 9724 50264 + [ScriptMethod(name: "钻石星辰", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9724|50264)$"], + userControl: true)] + public void 钻石星辰(Event ev, ScriptAccessory sa) + { + var myIndex = sa.GetMyIndex(); + sa.Method.TextInfo($"AOE,场中集合{(myIndex == 3 ? ",即将滑冰" : "")}", 2500, true); + sa.Method.TTS($"AOE,场中集合{(myIndex == 3 ? ",即将滑冰" : "")}"); + } + + // 大气爆发 9726 50266 + [ScriptMethod(name: "大气爆发", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9726|50266)$"], + userControl: true)] + public void 大气爆发(Event ev, ScriptAccessory sa) + { + sa.DrawKnockBack(Center1, 0, 10000, $"大气爆发", 3f, 20f, sa.Data.DefaultDangerColor.WithW(2f)); + } + + // 螺旋冲锋 9731 50271 + [ScriptMethod(name: "螺旋冲锋", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9731|50271)$"], + userControl: true)] + public void 螺旋冲锋(Event ev, ScriptAccessory sa) + { + sa.DrawRect(ev.SourceId, 0, 6000, $"螺旋冲锋", 0, 60, 100, sa.Data.DefaultDangerColor.WithW(1.5f)); + } + + // 以太射线 9752 50292 + [ScriptMethod(name: "以太射线直线", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9752|50292)$"], + userControl: true)] + public void 以太射线直线(Event ev, ScriptAccessory sa) + { + var dp = sa.DrawRect(ev.SourceId, 0, 2500, $"以太射线", 0, 3, 100, sa.Data.DefaultDangerColor.WithW(2f), draw: false); + sa.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Rect, dp); + } + + // 万亿斩击 9803 50296 + [ScriptMethod(name: "万亿斩击", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9803|50343)$"], + userControl: true)] + public void 万亿斩击(Event ev, ScriptAccessory sa) + { + if (!sa.Data.MyObject!.IsTank()) return; + sa.Method.TextInfo($"死刑换T", 2500, true); + sa.Method.TTS($"死刑换T"); + } + + // 超新星P3 10015 50305 + [ScriptMethod(name: "超新星(P3)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(10015|50305)$"], + userControl: true)] + public void 超新星P3(Event ev, ScriptAccessory sa) + { + sa.Method.TextInfo($"集合,停止移动", 2500, true); + sa.Method.TTS($"集合,停止移动"); + } + + // 闪电P3 10016 50306 + [ScriptMethod(name: "闪电(P3)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(10016|50306)$"], + userControl: true)] + public void 闪电P3(Event ev, ScriptAccessory sa) + { + sa.Method.TextInfo($"集合,保持移动", 2500, true); + sa.Method.TTS($"集合,保持移动"); + } + + // 闪电旋风P3 10021 50309 + [ScriptMethod(name: "闪电旋风(P3)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(10021|50309)$"], + userControl: true)] + public void 闪电旋风P3(Event ev, ScriptAccessory sa) + { + if (MathTools.Debounce(ref 机制上次触发时间, 500)) + { + sa.Method.TextInfo($"快躲开!", 2500, true); + sa.Method.TTS($"快躲开!"); + }; + sa.DrawCircle(ev.EffectPosition, 0, 3000, $"闪电旋风", 5f, sa.Data.DefaultDangerColor.WithW(2f)); + } + + // 神龙啸月环 9800 50293 + [ScriptMethod(name: "神龙啸月环", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^(9800|50293)$"], + userControl: true)] + public void 神龙啸月环(Event ev, ScriptAccessory sa) + { + sa.DrawDonut(ev.SourceId, 0, 5000, $"神龙啸", 50, 10, sa.Data.DefaultDangerColor.WithW(2f)); + } + + #region 参数容器类 + private class ShinryuParams + { + public int 放尾巴次数 = 0; + public int 火焰链次数 = 0; + public bool 绿色地板裂纹 = false; + public int 死亡轮回次数 = 0; + public bool 是幻巧战 = false; + public bool 副本判断完毕 = false; + public void Reset(ScriptAccessory sa) + { + 放尾巴次数 = 0; + 火焰链次数 = 0; + 绿色地板裂纹 = false; + 死亡轮回次数 = 0; + 是幻巧战 = false; + 副本判断完毕 = false; + + sa.DebugMsg($"神龙参数 Reset", Debugging); + } + } + + #endregion 参数容器类 + +} + +#region 函数集 +internal 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"]); + } +} + +internal 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 List GetTetherSource(this ScriptAccessory sa, IBattleChara? battleChara, uint tetherId) + { + List tetherSourceId = []; + if (battleChara == null || !battleChara.IsValid()) return []; + unsafe + { + BattleChara* chara = (BattleChara*)battleChara.Address; + var tetherList = chara->Vfx.Tethers; + + foreach (var tether in tetherList) + { + if (tether.Id != tetherId) continue; + tetherSourceId.Add(tether.TargetId.ObjectId); + } + } + return tetherSourceId; + } + + public static unsafe byte? GetTransformationId(this ScriptAccessory sa, IGameObject? obj) + { + if (obj == null) return null; + Character* objStruct = (Character*)obj.Address; + return objStruct->Timeline.ModelState; + } +} +#region 计算函数 + +internal 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(); + + /// + /// 获得某角度所在划分区域 + /// + /// 输入弧度 + /// 区域划分数量 + /// 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 bool Debounce(ref long lastTick, int delayMs = 1000) + { + long tickCount64 = Environment.TickCount64; + if (tickCount64 - lastTick < delayMs) + return false; + lastTick = tickCount64; + return true; + } +} + +#endregion 计算函数 + +#region 位置序列函数 +internal static class IndexHelper +{ + /// + /// 获得主视角玩家对应的位置index + /// + /// + /// 主视角玩家对应的位置index + public static int GetMyIndex(this ScriptAccessory sa) + { + return sa.Data.PartyList.IndexOf(sa.Data.Me); + } + + /// + /// 输入位置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]; + } + +} +#endregion 位置序列函数 + +#region 绘图函数 + +internal 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, Vector4 color, + 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 = color; + 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, + Vector4 color, float rotation = 0, float width = 1f, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, 0, rotation, width, + width, 0, 0, DrawModeEnum.Imgui, DrawTypeEnum.Displacement, color, false, true, draw); + + public static DrawPropertiesEdit DrawGuidance(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, Vector4 color, float rotation = 0, float width = 1f, + bool draw = true) + => sa.DrawGuidance((ulong)sa.Data.Me, targetObj, delay, destroy, name, color, rotation, width, draw); + + /// + /// 返回圆形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 圆形径长 + /// 是否随时间扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawCircle(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float scale, Vector4 color, 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, color, byTime,false, draw); + + /// + /// 返回环形绘图 + /// + /// + /// 圆心 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 外径 + /// 内径 + /// 是否随时间扩充 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawDonut(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, + float outScale, float innerScale, Vector4 color, 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, color, 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, Vector4 color, bool byTime = false, bool draw = true) + => sa.DrawOwnerBase(ownerObj, targetObj, delay, destroy, name, radian, rotation, outScale, outScale, innerScale, + innerScale, DrawModeEnum.Default, innerScale == 0 ? DrawTypeEnum.Fan : DrawTypeEnum.Donut, color, 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, Vector4 color, bool byTime = false, bool draw = true) + => sa.DrawFan(ownerObj, 0, delay, destroy, name, radian, rotation, outScale, innerScale, color, 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, Vector4 color, 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, color, byTime, byY, draw); + + public static DrawPropertiesEdit DrawRect(this ScriptAccessory sa, + object ownerObj, int delay, int destroy, string name, float rotation, + float width, float length, Vector4 color, bool byTime = false, bool byY = false, bool draw = true) + => sa.DrawRect(ownerObj, 0, delay, destroy, name, rotation, width, length, color, byTime, byY, draw); + + /// + /// 返回击退绘图 + /// + /// + /// 击退源 + /// 延时 + /// 消失时间 + /// 绘图名字 + /// 箭头宽 + /// 箭头长 + /// 使用颜色 + /// 是否直接绘制 + /// + public static DrawPropertiesEdit DrawKnockBack(this ScriptAccessory sa, + object targetObj, int delay, int destroy, string name, float width, float length, + Vector4 color, bool draw = true) + => sa.DrawOwnerBase(sa.Data.Me, targetObj, delay, destroy, name, 0, float.Pi, width, length, 0, 0, + DrawModeEnum.Default, DrawTypeEnum.Displacement, color, 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, Vector4 color, 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, color, byTime, byY, draw); + +} + +#endregion 绘图函数 + +#region 调试函数 + +internal static class DebugFunction +{ + public static void DebugMsg(this ScriptAccessory sa, string msg, bool enable = true, bool showInChatBox = true) + { + if (!enable) return; + sa.Log.Debug(msg); + if (!showInChatBox) return; + sa.Method.SendChat($"/e {msg}"); + } +} + +#endregion 调试函数 + +#endregion 函数集 +