diff --git a/00-Other/Crucible_of_the_Unbroken.cs b/00-Other/Crucible_of_the_Unbroken.cs index 5e6e279..f64bbd8 100644 --- a/00-Other/Crucible_of_the_Unbroken.cs +++ b/00-Other/Crucible_of_the_Unbroken.cs @@ -25,15 +25,16 @@ using FFXIVClientStructs.FFXIV.Component.GUI; namespace Crucible_of_the_Unbroken; [ScriptType(guid: "eb6fe4f3-0f9f-40a8-9c76-daeb0a11554c", name: "斗兽奇弈", territorys: [1339, 1340, 1341, 1342, 1343], - version: "0.0.0.5", author: "Tetora", note: noteStr)] + version: "0.0.0.6", author: "Tetora", note: noteStr)] public class Crucible_of_the_Unbroken { const string noteStr = """ - v0.0.0.5: + v0.0.0.6: 斗兽奇弈 / 闘獣練 / Crucible of the Unbroken 初版绘制 + "逃课关闭部分无用提示" 将关闭大部分净化、驱散、打断等提示 """; #region 用户控制 @@ -44,6 +45,9 @@ public class Crucible_of_the_Unbroken [UserSetting("弹窗文本提示开关")] public bool isText { get; set; } = true; + [UserSetting("逃课关闭部分无用提示")] + public bool isCheeseStrat { get; set; } = false; + [UserSetting("兽道三额外提示开关")] public bool isThirdDegree { get; set; } = true; @@ -153,6 +157,7 @@ public class Crucible_of_the_Unbroken [ScriptMethod(name: "敌人#1: 奇子·骑士_骨化(打断提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^46871$"])] public void 骑士_骨化(Event @event, ScriptAccessory accessory) { + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"打断骑士伤害提高(控制或借用-咒具碎魂)", duration: 7000, false); if (isTTS) accessory.Method.TTS($"打断骑士"); } @@ -161,6 +166,7 @@ public class Crucible_of_the_Unbroken public void 骑士_物理伤害提高(Event @event, ScriptAccessory accessory) { if (HelperExtensions.GetCurrentTerritoryId() != 1339) return; + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"驱散骑士(借用-水栖波)", duration: 5000, false); if (isTTS) accessory.Method.TTS($"驱散骑士"); } @@ -227,6 +233,7 @@ public class Crucible_of_the_Unbroken public void 夺灵魔_澄澈之心(Event @event, ScriptAccessory accessory) { if (HelperExtensions.GetCurrentTerritoryId() != 1339) return; + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"驱散伤害提高(借用-水栖波)", duration: 5000, false); if (isTTS) accessory.Method.TTS($"驱散"); } @@ -411,6 +418,7 @@ public class Crucible_of_the_Unbroken public void 梦魔法师_盲信(Event @event, ScriptAccessory accessory) { // 赋予BOSS 伤害提高 StatusID 1225 + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"打断 <梦魔法师> 伤害提高(借用-咒具碎魂)", duration: 5000, false); if (isTTS) accessory.Method.TTS($"打断梦魔法师"); } @@ -942,6 +950,7 @@ public class Crucible_of_the_Unbroken public void 牛魔老弟_声援(Event @event, ScriptAccessory accessory) { // 伤害提高 StatusID 1225 + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"打断 <牛魔老弟> 伤害提高(借用-咒具碎魂)", duration: 6000, false); if (isTTS) accessory.Method.TTS($"打断牛魔老弟"); } @@ -1019,6 +1028,7 @@ public class Crucible_of_the_Unbroken [ScriptMethod(name: "头目: 路斯福洛克斯_小地豆肤(驱散提示)", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^5423$"])] public void 路斯福洛克斯_小地豆肤(Event @event, ScriptAccessory accessory) { + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"驱散BOSS(借用-水栖波)", duration: 5000, false); if (isTTS) accessory.Method.TTS($"驱散BOSS"); } @@ -1520,6 +1530,23 @@ public class Crucible_of_the_Unbroken // if (isText)accessory.Method.TextInfo($"三穿一", duration: 2000, false); if (isTTS) accessory.Method.TTS($"三穿一"); } + + [ScriptMethod(name: "敌人#1: 奇子·博学林鸮_浮空圈指路", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:regex:^2004354$", "Operate:Add","Kind:EventObj"])] + public void 博学林鸮_浮空圈(Event @event, ScriptAccessory accessory) + { + if (isText)accessory.Method.TextInfo($"前往浮空圈待机", duration: 5300, false); + if (isTTS)accessory.Method.TTS($"前往浮空圈待机"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"浮空圈连线{@event.SourceId}"; + dp.Owner = accessory.Data.Me; + dp.Color = accessory.Data.DefaultSafeColor; + dp.ScaleMode |= ScaleMode.YByDistance; + dp.TargetObject = @event.SourceId(); + dp.Scale = new(1f); + dp.DestoryAt = 7000; + accessory.Method.SendDraw(DrawModeEnum.Imgui, DrawTypeEnum.Displacement, dp); + } [ScriptMethod(name: "敌人#1: 奇子·博学林鸮_爆震之章(浮空提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48657$"])] public void 博学林鸮_爆震之章(Event @event, ScriptAccessory accessory) @@ -1546,6 +1573,7 @@ public class Crucible_of_the_Unbroken public void 博学林鸮_精神集中(Event @event, ScriptAccessory accessory) { // 赋予 StatusID 5020 魔法伤害提高 + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"准备驱散(借用-水栖波)", duration: 2000, false); if (isTTS) accessory.Method.TTS($"准备驱散"); } @@ -1560,6 +1588,7 @@ public class Crucible_of_the_Unbroken [ScriptMethod(name: "敌人#1: 奇子·博学林鸮的羽毛_暴风(击退提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48667$"])] public void 博学林鸮的羽毛_暴风(Event @event, ScriptAccessory accessory) { + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"吸引或击退 <博学林鸮的羽毛>", duration: 3000, true); if (isTTS) accessory.Method.TTS($"吸引或击退羽毛"); } @@ -1586,6 +1615,13 @@ public class Crucible_of_the_Unbroken dp1.DestoryAt = 6700; accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp1); } + + [ScriptMethod(name: "敌人#2: 奇子·魔界花_恶臭气(引导顺劈提示-仅兽三)", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^19642$"],suppress:300000)] + public void 魔界花_恶臭气引导提示(Event @event, ScriptAccessory accessory) + { + if (isThirdDegree && isText)accessory.Method.TextInfo($"引导顺劈面向", duration: 3400, true); + if (isThirdDegree && isTTS)accessory.Method.TTS($"引导顺劈面向"); + } [ScriptMethod(name: "敌人#2: 奇子·魔界花_恶臭气(顺劈)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^4867(3|5)$"])] public void 魔界花_恶臭气(Event @event, ScriptAccessory accessory) @@ -1599,14 +1635,52 @@ public class Crucible_of_the_Unbroken dp.DestoryAt = @event.ActionId == 48673 ? 4700 : 200; accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); } + + [ScriptMethod(name: "敌人#2: 奇子·魔界花_恶臭气(顺劈预测)", eventType: EventTypeEnum.TargetIcon, eventCondition: ["Id:regex:^02A(C|D)$"])] + public void 魔界花_恶臭气预测(Event @event, ScriptAccessory accessory) + { + // 黄顺 vfx/lockon/eff/m037_turning_right01x2.avfx 02AC + // 蓝逆 vfx/lockon/eff/m037_turning_left01x2.avfx 02AD + // 读条4.7s,约3.7s后出TargetIcon,4.2s后出omen,每次旋转45°,共5次 + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"恶臭气预测{@event.SourceId}"; + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.6f); + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(50f); + var rawId = @event["Id"]?.Trim('"'); + dp.Rotation = rawId == "02AC" ? 315f.DegToRad() : 45f.DegToRad(); + dp.Radian = 90f.DegToRad(); + dp.DestoryAt = 10000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Fan, dp); + } [ScriptMethod(name: "敌人#2: 奇子·魔界花_地震(击退)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^50758$"])] public void 魔界花_地震(Event @event, ScriptAccessory accessory) { + if (isTTS)accessory.Method.TTS($"中间击退"); IntPtr omenHandle = accessory.Method.VfxMethod.CreateOmen(203, new Vector3(35f), - @event.EffectPosition(), @event.SourceRotation(), new Vector4(1f, 0.5f, 0f, 0.5f), 4700); + @event.EffectPosition(), @event.SourceRotation(), new Vector4(1f, 0.5f, 0f, 0.4f), 4700); - // Todo.击退距离没测 + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"魔界花_地震击退预测{@event.SourceId}"; + dp.Scale = new(1f, 10f); + dp.Color = accessory.Data.DefaultDangerColor.WithW(2f); + dp.Owner = accessory.Data.Me; + dp.TargetObject = @event.SourceId(); + dp.Rotation = float.Pi; + dp.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + + var dp1 = accessory.Data.GetDefaultDrawProperties(); + dp1.Name = $"魔界花_地震击退安全区{@event.SourceId}"; + dp1.Color = accessory.Data.DefaultSafeColor.WithW(10f); + dp1.Owner = @event.SourceId(); + dp1.Scale = new Vector2(10f); + dp1.InnerScale = new Vector2(9.94f); // 毒圈为6m + dp1.Radian = float.Pi * 2; + dp1.DestoryAt = 4700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp1); } [ScriptMethod(name: "敌人#2: 奇子·魔界花_藤鞭穿刺(直线)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48682$"])] @@ -1620,6 +1694,18 @@ public class Crucible_of_the_Unbroken dp.DestoryAt = 4700; accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Rect, dp); } + + [ScriptMethod(name: "敌人#2: 奇子·大口花_酸雾(钢铁)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48679$"])] + public void 大口花_酸雾(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"大口花_酸雾{@event.SourceId}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(6f); + dp.DestoryAt = 3700; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } [ScriptMethod(name: "随机#1: 奇子·花苗_荆棘幼苗(危险区)", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["Operate:Add", "Kind:EventObj", "DataId:2015458"])] public void 花苗_荆棘幼苗(Event @event, ScriptAccessory accessory) @@ -1905,16 +1991,24 @@ public class Crucible_of_the_Unbroken accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Donut, dp); } - [ScriptMethod(name: "敌人#3: 奇子·树精_橡果炸弹(打断提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^50518$"])] + [ScriptMethod(name: "敌人#3: 奇子·树精_橡果炸弹(睡眠提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^50518$"])] public void 树精_橡果炸弹(Event @event, ScriptAccessory accessory) { - if (isText) accessory.Method.TextInfo($"睡眠点名", duration: 4000, false); - if (isTTS) accessory.Method.TTS($"睡眠点名"); + if (isText) accessory.Method.TextInfo(isThirdDegree ? "睡眠点名(宝宝挑衅)" : "睡眠点名", duration: 4000, true); + if (isTTS) accessory.Method.TTS(isThirdDegree ? "睡眠点名(宝宝挑衅)" : "睡眠点名"); + } + + [ScriptMethod(name: "敌人#3: 奇子·树精_橡果炸弹(挑衅提醒-仅兽三)", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^48781$"])] + public void 树精_兽三橡果炸弹挑衅提示(Event @event, ScriptAccessory accessory) + { + if (isThirdDegree && isText)accessory.Method.TextInfo($"挑衅", duration: 3000, true); + if (isThirdDegree && isTTS)accessory.Method.TTS($"挑衅"); } [ScriptMethod(name: "敌人#3: 奇子·比洛克_治疗之水(打断提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48782$"])] public void 比洛克_治疗之水(Event @event, ScriptAccessory accessory) { + if (isCheeseStrat) return; if (isText) accessory.Method.TextInfo($"打断 <比洛克> (借用-咒具碎魂)", duration: 8000, false); if (isTTS) accessory.Method.TTS($"打断比洛克"); } @@ -2123,12 +2217,21 @@ public class Crucible_of_the_Unbroken { if (isText)accessory.Method.TextInfo($"四连放圈点名", duration: 12300, false); if (isTTS)accessory.Method.TTS($"四连放圈点名"); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"猛毒粘液放圈{@event.TargetId}"; + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.4f); + dp.Owner = @event.TargetId(); + dp.Scale = new Vector2(6f); + dp.Delay = 3200; + dp.DestoryAt = 11000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); } [ScriptMethod(name: "头目: 怨毒龙 博尔格尼_蠕动的痰(放小怪提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^4881(7|9)$"])] public void 博尔格尼_蠕动的痰(Event @event, ScriptAccessory accessory) { - if (isText)accessory.Method.TextInfo (@event.ActionId == 48817 ? $"放置小怪点名" : "快躲开", duration: 6900, true); + if (isText)accessory.Method.TextInfo (@event.ActionId == 48817 ? $"放置小怪点名" : "快躲开", duration: 4600, true); if (isTTS)accessory.Method.TTS (@event.ActionId == 48817 ? $"放置小怪点名" : $"快躲开"); } @@ -2147,6 +2250,9 @@ public class Crucible_of_the_Unbroken [ScriptMethod(name: "头目: 怨毒龙 博尔格尼_空降(蓝圈击退)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^48828$"])] public void 博尔格尼_空降(Event @event, ScriptAccessory accessory) { + if (isThirdDegree && isText)accessory.Method.TextInfo($"大AOE(物理)", duration: 3000, true); + if (isThirdDegree && isTTS)accessory.Method.TTS($"物理AOE"); + var dp = accessory.Data.GetDefaultDrawProperties(); dp.Name = $"博尔格尼_空降击退预测{@event.SourceId}"; dp.Scale = new(1f, 30f); @@ -2544,6 +2650,7 @@ public class Crucible_of_the_Unbroken [ScriptMethod(name: "敌人#2: 奇子·火蛟_火棘屏障(读条驱散提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^49253$"])] public void 火蛟_火棘屏障(Event @event, ScriptAccessory accessory) { + // if (isCheeseStrat) return; if (isText)accessory.Method.TextInfo($"准备驱散 <火蛟>(借用-水栖波)", duration: 5000, false); if (isTTS)accessory.Method.TTS($"准备驱散火蛟"); } @@ -3048,6 +3155,7 @@ public class Crucible_of_the_Unbroken [ScriptMethod(name: "敌人#4: 凝胶化火焰/雷电_炸裂(打断提示)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^4937(3|5)$"])] public void 凝胶化_炸裂(Event @event, ScriptAccessory accessory) { + // if (isCheeseStrat) return; if (isTTS)accessory.Method.TTS($"打断史莱姆"); } @@ -3467,7 +3575,7 @@ public class Crucible_of_the_Unbroken dp.Name = $"高段剧毒魔刃{@event.SourceId}"; dp.Scale = new (10f, 30f); dp.Owner = @event.SourceId(); - dp.Color = accessory.Data.DefaultDangerColor.WithW(0.6f); + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.5f); dp.Delay = 4200; dp.DestoryAt = 8400; @@ -3485,7 +3593,7 @@ public class Crucible_of_the_Unbroken { var dp = accessory.Data.GetDefaultDrawProperties(); dp.Name = $"火焰魔刃·牢狱{@event.SourceId}"; // ActionId 49460 - dp.Color = accessory.Data.DefaultDangerColor.WithW(0.9f); + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.8f); dp.Owner = @event.SourceId(); dp.Scale = new Vector2(12f); dp.DestoryAt = 11800; @@ -3504,7 +3612,7 @@ public class Crucible_of_the_Unbroken { var dp = accessory.Data.GetDefaultDrawProperties(); dp.Name = $"高段魔刃爆焰{@event.SourceId}"; - dp.Color = accessory.Data.DefaultDangerColor.WithW(1f); + dp.Color = accessory.Data.DefaultDangerColor.WithW(0.8f); dp.Owner = @event.SourceId(); dp.Scale = new Vector2(20f); dp.DestoryAt = 10700; @@ -3633,6 +3741,14 @@ public static class EventExtensions return JsonConvert.DeserializeObject(@event["Param"]); } + public static uint Id(this Event @event) + { + var raw = @event["Id"]; + if (string.IsNullOrWhiteSpace(raw)) return 0; + raw = raw.Trim('"'); + return Convert.ToUInt32(raw, 16); + } + public static uint Id1(this Event @event) { return JsonConvert.DeserializeObject(@event["Id1"]); diff --git a/Deep_Dungeon/Heaven-on-High.cs b/Deep_Dungeon/Heaven-on-High.cs new file mode 100644 index 0000000..db25447 --- /dev/null +++ b/Deep_Dungeon/Heaven-on-High.cs @@ -0,0 +1,739 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Numerics; +using System.Collections.Generic; +using Newtonsoft.Json; +using Dalamud.Utility.Numerics; +using KodakkuAssist.Script; +using KodakkuAssist.Module.GameEvent; +using KodakkuAssist.Module.Draw; +using KodakkuAssist.Data; +using KodakkuAssist.Extensions; +using System.Threading.Tasks; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Collections.Generic; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; + +namespace Heaven_on_High; + +[ScriptType(guid: "c63d669e-6a74-4338-a158-09673fabdc8d", name: "天之御柱", + territorys: [770,771,772,773,774,775,780,782,783,784,785], + version: "0.0.0.1", author: "Tetora", note: noteStr)] + + // 1~10层: + // 11~20层: + // 21~30层: + // 31~40层: 782 + // 41~50层: + // 51~60层: + // 61~70层: + // 71~80层: + // 81~90层: + // 91~100层: + +public class Heaven_on_High { + const string noteStr = + """ + v0.0.0.1: + 天之御柱绘制 + """; + + #region 各种记录 + + /* StatusID + * 眩晕 2(如近战扫腿、T下踢) + * 睡眠 3(如法系催眠、奶妈沉静) + * 变身 565 + * 诅咒 1087 (拟态怪 - 怨念) + * 失明 1088 + * 最大体力减少 1089 + * 伤害降低 1090 + * 加速 1091 + * 能力封印 1092 + * 最大体力魔力提升 1093 + * 禁止使用道具 1094 + * 禁止冲刺 1095 + * 击退无效 1096 + * 禁止体力自然恢复 1097 + * 无法发动技能 1113 (形态变化) + */ + + #endregion + + #region 基础控制 + + [UserSetting("TTS开关")] + public bool isTTS { get; set; } = true; + + [UserSetting("弹窗文本提示开关")] + public bool isText { get; set; } = true; + + [UserSetting("启用小工具(已确认设置完毕)")] + public bool isMiniTools { get; set; } = false; + + [UserSetting("设置传送灯笼查找颜色")] + public ScriptColor teleporter { get; set; } = new() { V4 = new(0f, 1f, 0f, 2f) }; + + [UserSetting("开发者模式")] + public bool isDeveloper { get; set; } = false; + + #endregion + + #region 全局变量控制 + + [ScriptMethod(name: "伤头&插言 打断销毁", eventType: EventTypeEnum.ActionEffect, eventCondition: ["ActionId:regex:^75(38|51)$"], userControl: false)] + public void 打断销毁(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($".*{@event.TargetId()}"); + } + + [ScriptMethod(name: "特殊状态销毁", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:regex:^(2|3|1511|1113)$"], userControl: false)] + public void 特殊状态销毁(Event @event, ScriptAccessory accessory) + { + // 赋予对应怪物 眩晕[2]、睡眠[3]、石化[1511]、(形态变化)无法发动技能[1113] 等状态都需要销毁绘图 + accessory.Method.RemoveDraw($".*{@event.TargetId()}"); + } + + [ScriptMethod(name: "咏唱中断销毁", eventType: EventTypeEnum.CancelAction, eventCondition: [], userControl: false)] + public void 咏唱中断销毁(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($".*{@event.SourceId()}"); + } + + [ScriptMethod(name: "死亡销毁", eventType: EventTypeEnum.Death, eventCondition: [], userControl: false)] + public void 死亡销毁(Event @event, ScriptAccessory accessory) + { + accessory.Method.RemoveDraw($".*{@event.TargetId()}"); + } + + public bool KnockPenalty = false; + + [ScriptMethod(name: "天气:击退无效添加", eventType: EventTypeEnum.StatusAdd, eventCondition: ["StatusID:1096"],userControl: false)] + public void 上击退buff(Event @event, ScriptAccessory accessory) + { + KnockPenalty = true; + } + + [ScriptMethod(name: "天气:击退无效移除", eventType: EventTypeEnum.StatusRemove, eventCondition: ["StatusID:1096"],userControl: false)] + public void 下击退buff(Event @event, ScriptAccessory accessory) + { + KnockPenalty = false; + } + + #endregion + + #region 小工具部分 + + [ScriptMethod(name: "—————— 小工具部分(先自行关闭不需要的功能) / Tools ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 小工具部分(Event @event, ScriptAccessory accessory) { } + + [ScriptMethod(name: "传送灯笼查找 / Teleportation Construct Finder", eventType: EventTypeEnum.ObjectChanged, eventCondition: ["DataId:2009507", "Operate:Add"])] + public void 传送装置查找(Event @event, ScriptAccessory accessory) + { + if(!isMiniTools) return; + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"传送装置查找"; + dp.Owner = accessory.Data.Me; + dp.TargetObject = @event.SourceId(); + dp.Scale = new (1f); + dp.ScaleMode |= ScaleMode.YByDistance; + dp.Color = teleporter.V4; + dp.DestoryAt = 3000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + #endregion + + /* 还不知道天宫的技能ID + [ScriptMethod(name: "抖动的宝箱_怨念提示", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 抖动的宝箱_怨念(Event @event, ScriptAccessory accessory) + { + if (isText) accessory.Method.TextInfo("打断拟态怪", duration: 2300, true); + if (isTTS) accessory.Method.TTS("打断拟态怪"); + } + */ + + #region 1~10层 + + [ScriptMethod(name: "—————— 1 ~ 10 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第1层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 11~20层 + + [ScriptMethod(name: "—————— 11 ~ 20 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第11层(Event @event, ScriptAccessory accessory) { } + + [ScriptMethod(name: "40 怖军_龙卷(AOE)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^11902$"])] + public void 怖军_龙卷(Event @event, ScriptAccessory accessory) + { + if (isTTS)accessory.Method.TTS($"AOE"); + } + + [ScriptMethod(name: "40 怖军_古代暴风(击退)", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:regex:^11904$"])] + public void 怖军_古代暴风(Event @event, ScriptAccessory accessory) + { + if (isText)accessory.Method.TextInfo($"击退到安全区", duration: 4300, true); + if (isTTS)accessory.Method.TTS($"击退到安全区"); + + IntPtr omenHandle = accessory.Method.VfxMethod.CreateOmen(203, new Vector3(50f), + @event.EffectPosition(), @event.SourceRotation(), new Vector4(1f,1f,0f,0.2f), 5000); + accessory.Method.VfxMethod.SetVfxSpeed(omenHandle,1f); + + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"怖军_古代暴风击退预测{@event.SourceId}"; + dp.Scale = new(1f, 25f); + dp.Color = accessory.Data.DefaultDangerColor.WithW(2f); + dp.Owner = accessory.Data.Me; + dp.TargetObject = @event.SourceId(); + dp.Rotation = float.Pi; + dp.DestoryAt = 5000; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Displacement, dp); + } + + [ScriptMethod(name: "40 旋风_风压(钢铁)", eventType: EventTypeEnum.AddCombatant, eventCondition: ["DataId:regex:^9187$"])] + public void 旋风_风压(Event @event, ScriptAccessory accessory) + { + var dp = accessory.Data.GetDefaultDrawProperties(); + dp.Name = $"旋风_风压{@event.SourceId}"; + dp.Color = accessory.Data.DefaultDangerColor; + dp.Owner = @event.SourceId(); + dp.Scale = new Vector2(6f); + dp.DestoryAt = 5800; + accessory.Method.SendDraw(DrawModeEnum.Default, DrawTypeEnum.Circle, dp); + } + + #endregion + + #region 21~30层 + + [ScriptMethod(name: "—————— 21 ~ 30 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第21层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 31~40层 + + [ScriptMethod(name: "—————— 31 ~ 40 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第31层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 41~50层 + + [ScriptMethod(name: "—————— 41 ~ 50 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第41层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 51~60层 + + [ScriptMethod(name: "—————— 51 ~ 60 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第51层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 61~70层 + + [ScriptMethod(name: "—————— 61 ~ 70 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第61层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 71~80层 + + [ScriptMethod(name: "—————— 71 ~ 80 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第71层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 81~90层 + + [ScriptMethod(name: "—————— 81 ~ 90 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第81层(Event @event, ScriptAccessory accessory) { } + + #endregion + + #region 91~100层 + + [ScriptMethod(name: "—————— 91 ~ 100 层 ——————", eventType: EventTypeEnum.StartCasting, eventCondition: ["ActionId:"])] + public void 第91层(Event @event, ScriptAccessory accessory) { } + + #endregion + +} + + +public static class EventExtensions +{ + private static bool ParseHexId(string? idStr, out uint id) + { + id = 0; + if (string.IsNullOrEmpty(idStr)) return false; + try + { + var idStr2 = idStr.Replace("0x", ""); + id = uint.Parse(idStr2, System.Globalization.NumberStyles.HexNumber); + return true; + } + catch (Exception) + { + return false; + } + } + + public static uint ActionId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["ActionId"]); + } + + public static uint SourceId(this Event @event) + { + return ParseHexId(@event["SourceId"], out var id) ? id : 0; + } + + public static uint SourceDataId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceDataId"]); + } + + public static uint Command(this Event @event) + { + return ParseHexId(@event["Command"], out var cid) ? cid : 0; + } + + public static uint DurationMilliseconds(this Event @event) + { + return JsonConvert.DeserializeObject(@event["DurationMilliseconds"]); + } + + public static float SourceRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourceRotation"]); + } + + public static float TargetRotation(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetRotation"]); + } + + public static byte Index(this Event @event) + { + return (byte)(ParseHexId(@event["Index"], out var index) ? index : 0); + } + + public static uint State(this Event @event) + { + return ParseHexId(@event["State"], out var state) ? state : 0; + } + + public static string SourceName(this Event @event) + { + return @event["SourceName"]; + } + + public static string TargetName(this Event @event) + { + return @event["TargetName"]; + } + + public static uint TargetId(this Event @event) + { + return ParseHexId(@event["TargetId"], out var id) ? id : 0; + } + + public static Vector3 SourcePosition(this Event @event) + { + return JsonConvert.DeserializeObject(@event["SourcePosition"]); + } + + public static Vector3 TargetPosition(this Event @event) + { + return JsonConvert.DeserializeObject(@event["TargetPosition"]); + } + + public static Vector3 EffectPosition(this Event @event) + { + return JsonConvert.DeserializeObject(@event["EffectPosition"]); + } + + public static uint DirectorId(this Event @event) + { + return ParseHexId(@event["DirectorId"], out var id) ? id : 0; + } + + public static uint StatusId(this Event @event) + { + return JsonConvert.DeserializeObject(@event["StatusId"]); + } + + public static uint StackCount(this Event @event) + { + return JsonConvert.DeserializeObject(@event["StackCount"]); + } + + public static uint Param(this Event @event) + { + return JsonConvert.DeserializeObject(@event["Param"]); + } +} + +public static class EnumExtensions +{ + public static string GetDescription(this Enum value) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = field?.GetCustomAttributes(typeof(DescriptionAttribute), false) + .FirstOrDefault() as DescriptionAttribute; + return attribute?.Description ?? value.ToString(); + } +} + +public static class MathTools +{ + public static float DegToRad(this float deg) => (deg + 360f) % 360f / 180f * float.Pi; + public static float RadToDeg(this float rad) => (rad + 2 * float.Pi) % (2 * float.Pi) / float.Pi * 180f; + + /// + /// 获得任意点与中心点的弧度值,以(0, 0, 1)方向为0,以(1, 0, 0)方向为pi/2。 + /// 即,逆时针方向增加。 + /// + /// 任意点 + /// 中心点 + /// + public static float GetRadian(this Vector3 point, Vector3 center) + => MathF.Atan2(point.X - center.X, point.Z - center.Z); + + /// + /// 获得任意点与中心点的长度。 + /// + /// 任意点 + /// 中心点 + /// + public static float GetLength(this Vector3 point, Vector3 center) + => new Vector2(point.X - center.X, point.Z - center.Z).Length(); + + /// + /// 将任意点以中心点为圆心,逆时针旋转并延长。 + /// + /// 任意点 + /// 中心点 + /// 旋转弧度 + /// 基于该点延伸长度 + /// + public static Vector3 RotateAndExtend(this Vector3 point, Vector3 center, float radian, float length) + { + var baseRad = point.GetRadian(center); + var baseLength = point.GetLength(center); + var rotRad = baseRad + radian; + return new Vector3( + center.X + MathF.Sin(rotRad) * (length + baseLength), + center.Y, + center.Z + MathF.Cos(rotRad) * (length + baseLength) + ); + } + + /// + /// 获得某角度所在划分区域 + /// + /// 输入弧度 + /// 区域划分数量 + /// 0度所在区域的初始Idx> + /// 是否为斜分割,默认为false + /// 是否顺时针增加,默认为false + /// + public static int RadianToRegion(this float radian, int regionNum, int baseRegionIdx = 0, bool isDiagDiv = false, bool isCw = false) + { + var sepRad = float.Pi * 2 / regionNum; + var inputAngle = radian * (isCw ? -1 : 1) + (isDiagDiv ? sepRad / 2 : 0); + var rad = (inputAngle + 4 * float.Pi) % (2 * float.Pi); + return ((int)Math.Floor(rad / sepRad) + baseRegionIdx + regionNum) % regionNum; + } + + /// + /// 将输入点左右折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointHorizon(this Vector3 point, float centerX) + => point with { X = 2 * centerX - point.X }; + + /// + /// 将输入点上下折叠 + /// + /// 待折叠点 + /// 中心折线坐标点 + /// + public static Vector3 FoldPointVertical(this Vector3 point, float centerZ) + => point with { Z = 2 * centerZ - point.Z }; + + /// + /// 将输入点中心对称 + /// + /// 输入点 + /// 中心点 + /// + public static Vector3 PointCenterSymmetry(this Vector3 point, Vector3 center) + => point.RotateAndExtend(center, float.Pi, 0); + + /// + /// 获取给定数的指定位数 + /// + /// 给定数值 + /// 对应位数,个位为1 + /// + public static int GetDecimalDigit(this int val, int x) + { + var valStr = val.ToString(); + var length = valStr.Length; + if (x < 1 || x > length) return -1; + var digitChar = valStr[length - x]; // 从右往左取第x位 + return int.Parse(digitChar.ToString()); + } +} + +public enum MarkType +{ + None = -1, + Attack1 = 0, + Attack2 = 1, + Attack3 = 2, + Attack4 = 3, + Attack5 = 4, + Bind1 = 5, + Bind2 = 6, + Bind3 = 7, + Ignore1 = 8, + Ignore2 = 9, + Square = 10, + Circle = 11, + Cross = 12, + Triangle = 13, + Attack6 = 14, + Attack7 = 15, + Attack8 = 16, + Count = 17 +} + +public static class IbcHelper +{ + public static IGameObject? GetById(this ScriptAccessory sa, ulong gameObjectId) + { + return sa.Data.Objects.SearchById(gameObjectId); + } + + public static IGameObject? GetMe(this ScriptAccessory sa) + { + return sa.Data.Objects.LocalPlayer; + } + + public static IEnumerable GetByDataId(this ScriptAccessory sa, uint dataId) + { + return sa.Data.Objects.Where(x => x.DataId == dataId); + } + + public static string GetPlayerJob(this ScriptAccessory sa, IPlayerCharacter? playerObject, bool fullName = false) + { + if (playerObject == null) return "None"; + return fullName ? playerObject.ClassJob.Value.Name.ToString() : playerObject.ClassJob.Value.Abbreviation.ToString(); + } + + public static float GetStatusRemainingTime(this ScriptAccessory sa, IBattleChara? battleChara, uint statusId) + { + if (battleChara == null || !battleChara.IsValid()) return 0; + unsafe + { + BattleChara* charaStruct = (BattleChara*)battleChara.Address; + var statusIdx = charaStruct->GetStatusManager()->GetStatusIndex(statusId); + return charaStruct->GetStatusManager()->GetRemainingTime(statusIdx); + } + } + + public static bool HasStatus(this ScriptAccessory sa, IBattleChara? battleChara, uint statusId) + { + if (battleChara == null || !battleChara.IsValid()) return false; + unsafe + { + BattleChara* charaStruct = (BattleChara*)battleChara.Address; + var statusIdx = charaStruct->GetStatusManager()->GetStatusIndex(statusId); + return statusIdx != -1; + } + } + + /// + /// 获取指定标记索引的对象EntityId + /// + public static unsafe ulong GetMarkerEntityId(uint markerIndex) + { + var markingController = MarkingController.Instance(); + if (markingController == null) return 0; + if (markerIndex >= 17) return 0; + + return markingController->Markers[(int)markerIndex]; + } + + /// + /// 获取对象身上的标记 + /// + /// MarkType + public static MarkType GetObjectMarker(IGameObject? obj) + { + if (obj == null || !obj.IsValid()) return MarkType.None; + + ulong targetEntityId = obj.EntityId; + + for (uint i = 0; i < 17; i++) + { + var markerEntityId = GetMarkerEntityId(i); + if (markerEntityId == targetEntityId) + { + return (MarkType)i; + } + } + + return MarkType.None; + } + + /// + /// 检查对象是否有指定的标记 + /// + public static bool HasMarker(IGameObject? obj, MarkType markType) + { + return GetObjectMarker(obj) == markType; + } + + /// + /// 检查对象是否有任何标记 + /// + public static bool HasAnyMarker(IGameObject? obj) + { + return GetObjectMarker(obj) != MarkType.None; + } + + private static ulong GetMarkerForObject(IGameObject? obj) + { + if (obj == null) return 0; + unsafe + { + for (uint i = 0; i < 17; i++) + { + var markerEntityId = GetMarkerEntityId(i); + if (markerEntityId == obj.EntityId) + { + return markerEntityId; + } + } + } + return 0; + } + + private static MarkType GetMarkerTypeForObject(IGameObject? obj) + { + if (obj == null) return MarkType.None; + unsafe + { + for (uint i = 0; i < 17; i++) + { + var markerEntityId = GetMarkerEntityId(i); + if (markerEntityId == obj.EntityId) + { + return (MarkType)i; + } + } + } + return MarkType.None; + } + + /// + /// 获取标记的名称 + /// + public static string GetMarkerName(MarkType markType) + { + return markType switch + { + MarkType.Attack1 => "攻击1", + MarkType.Attack2 => "攻击2", + MarkType.Attack3 => "攻击3", + MarkType.Attack4 => "攻击4", + MarkType.Attack5 => "攻击5", + MarkType.Bind1 => "止步1", + MarkType.Bind2 => "止步2", + MarkType.Bind3 => "止步3", + MarkType.Ignore1 => "禁止1", + MarkType.Ignore2 => "禁止2", + MarkType.Square => "方块", + MarkType.Circle => "圆圈", + MarkType.Cross => "十字", + MarkType.Triangle => "三角", + MarkType.Attack6 => "攻击6", + MarkType.Attack7 => "攻击7", + MarkType.Attack8 => "攻击8", + _ => "无标记" + }; + } + + public static float GetHitboxRadius(IGameObject obj) + { + if (obj == null || !obj.IsValid()) return -1; + return obj.HitboxRadius; + } + +} + +public static class HelperExtensions +{ + public static unsafe uint GetCurrentTerritoryId() + { + return AgentMap.Instance()->CurrentTerritoryId; // 额外进行地图ID判断 + } +} + +#region 特殊函数 +public unsafe static class ExtensionVisibleMethod +{ + public static bool IsCharacterVisible(this ICharacter chr) + { + var v = (IntPtr)(((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)chr.Address)->GameObject.DrawObject); + if (v == IntPtr.Zero) return false; + return Bitmask.IsBitSet(*(byte*)(v + 136), 0); + } + + public static class Bitmask + { + public static bool IsBitSet(ulong b, int pos) + { + return (b & (1UL << pos)) != 0; + } + + public static void SetBit(ref ulong b, int pos) + { + b |= 1UL << pos; + } + + public static void ResetBit(ref ulong b, int pos) + { + b &= ~(1UL << pos); + } + + public static bool IsBitSet(byte b, int pos) + { + return (b & (1 << pos)) != 0; + } + + public static bool IsBitSet(short b, int pos) + { + return (b & (1 << pos)) != 0; + } + } +} +#endregion 特殊函数 \ No newline at end of file diff --git a/OnlineRepo.json b/OnlineRepo.json index e6cb439..77fa1ac 100644 --- a/OnlineRepo.json +++ b/OnlineRepo.json @@ -1056,12 +1056,12 @@ { "Name": "斗兽奇弈", "Guid": "eb6fe4f3-0f9f-40a8-9c76-daeb0a11554c", - "Version": "0.0.0.5", + "Version": "0.0.0.6", "Author": "Tetora", "Repo": "https://github.com/Hibiya615/TetoraKAScript/tree/main", "DownloadUrl": "https://raw.githubusercontent.com/Hibiya615/TetoraKAScript/refs/heads/main/00-Other/Crucible_of_the_Unbroken.cs", - "Note": "v0.0.0.5:\r\n斗兽奇弈 / 闘獣練 / Crucible of the Unbroken\r\n初版绘制", - "UpdateInfo": "v0.0.0.5: 增加博学林鸮选集绘制", + "Note": "v0.0.0.6:\r\n斗兽奇弈 / 闘獣練 / Crucible of the Unbroken\r\n初版绘制", + "UpdateInfo": "v0.0.0.6: 增加部分机制绘制", "UpdateTime": "", "TerritoryIds": [ 1339,