diff --git a/Presets/Shadowbringers/Duties/Ultimate - The Epic of Alexander.md b/Presets/Shadowbringers/Duties/Ultimate - The Epic of Alexander.md index a0d9c43e..d74fbd34 100644 --- a/Presets/Shadowbringers/Duties/Ultimate - The Epic of Alexander.md +++ b/Presets/Shadowbringers/Duties/Ultimate - The Epic of Alexander.md @@ -17,6 +17,16 @@ Cruise Chaser Conal Cleaves: Shows CC's conal cleaves on odd marked players so y https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA_P2_Transition.cs ``` +[International] [Beta] [Script] P2 Transition 1211 script. +- show exaflares +- mark bait locations for 1211 strat + +> [!NOTE] +> The displayed text is in Japanese, so please change it accordingly in the config. +``` +https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA%20P2%201211%20Transition.cs +``` + # P2: BJ/CC P2/P3 Chakram Line AOEs: Works for Wormhole as well ``` @@ -39,6 +49,19 @@ Water/Lightning Text Reminder: Adds Water/Lightning text at your feet if you hav ``` ~Lv2~{"Name":"P2 BPOG Positions","Group":"TEA","ZoneLockH":[887],"Scenes":[2],"DCond":5,"ElementsL":[{"Name":"α","refX":89.82,"refY":100.0,"color":3372172800,"thicc":5.0,"overlayText":"α"},{"Name":"γ","refX":92.80687,"refY":100.0,"color":3372155119,"thicc":5.0,"overlayText":"γ"},{"Name":"β","refX":96.385,"refY":100.0,"refZ":-1.9073486E-06,"color":3355478271,"thicc":5.0,"overlayText":"β"},{"Name":"δ","refX":99.91906,"refY":100.0,"refZ":1.9073486E-06,"color":3357277952,"thicc":5.0,"overlayText":"δ"}],"UseTriggers":true,"Triggers":[{"Type":2,"Duration":19.0,"MatchIntl":{"En":"Initiating new combat protocol... Commence final judgment!"},"MatchDelay":33.0}]} ``` + +[Jp] [Beta] [Script] Nisi + +Show where to receive your nai-sai from the second time. + +It works in another language, but the displayed text is Japanese. + +No configuration required. + +``` +https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA%20P2%20Nisi.cs +``` + # P3: Alexander Prime Alexander Intermission Debuffs; Text reminder of what debuff you have, only supports English. @@ -64,6 +87,34 @@ Wormhole Soak Order (untested) for strat https://ff14.toolboxgaming.space/?id=23 ~Lv2~{"Name":"TEA Wormhole soak 8","Group":"TEA","ZoneLockH":[887],"Scenes":[7],"DCond":5,"ElementsL":[{"Name":"","type":1,"radius":6.0,"overlayPlaceholders":true,"thicc":5.0,"overlayText":"2nd","refActorDataID":2007520,"refActorComparisonType":3,"tether":true,"LimitDistance":true,"DistanceSourceX":112.0,"DistanceSourceY":100.0,"DistanceSourceZ":1.9073486E-06,"DistanceMax":10.0}],"UseTriggers":true,"Triggers":[{"Type":2,"Duration":5.0,"Match":"vfx/lockon/eff/m0361trg_a8t.avfx spawned on me","MatchDelay":13.0}]} ``` +[Jp] [Beta] [Script] Temporal Stasis + +Show your bait position. + +strat https://ff14.toolboxgaming.space/?id=860745463802461&preview=1 + +Configuration: + +- Select where you stand during each debuff. + +``` +https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA%20P2%20Temporal%20Stasis.cs +``` + +[Jp] [Beta] [Script] Wormhole Formation + +Show your bait position. + +strat https://www.youtube.com/watch?v=utfUGDM1Y9w&t=0s (JP) + +It is called `34固定`. + +No configuration required. + +``` +https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA%20P3%20Wormhole%20Formation.cs +``` + # P4: Perfect Alexander Trines Dodges: Need to import first two, third is optional. Indicates where the first dodge is (where to start/3rd Trine) and where the safe spot is (1st Trine). Has an optional marker for 2nd Trines. Supports English. ``` @@ -75,3 +126,19 @@ Trines Dodges: Need to import first two, third is optional. Indicates where the ``` ~Lv2~{"Enabled":false,"Name":"P4 Trines 2","Group":"TEA","ZoneLockH":[887],"DCond":5,"ElementsL":[{"Name":"","type":1,"radius":0.0,"color":65352,"thicc":10.2,"overlayText":"!!AVOID!!","refActorDataID":9020,"refActorRequireCast":true,"refActorCastId":[18574,18575,18576],"refActorUseCastTime":true,"refActorCastTimeMax":0.5,"refActorComparisonType":3}],"UseTriggers":true,"Triggers":[{"Type":2,"MatchIntl":{"En":"Perfect Alexander readies Almighty Judgment."},"MatchDelay":7.0}],"Freezing":true,"FreezeFor":15.0,"IntervalBetweenFreezes":15.0} ``` + +[Jp] [Beta] [Script] Fate Projection α + +No configuration required. + +``` +https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA%20P4%20Fate%20Projection%20α.cs +``` + +[JP] [Beta] [Script] Fate Projection β + +No configuration required. + +``` +https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA%20P4%20Fate%20Projection%20β.cs +``` \ No newline at end of file diff --git a/SplatoonScripts/Duties/Shadowbringers/TEA P2 1211 Transition.cs b/SplatoonScripts/Duties/Shadowbringers/TEA P2 1211 Transition.cs new file mode 100644 index 00000000..f32f0417 --- /dev/null +++ b/SplatoonScripts/Duties/Shadowbringers/TEA P2 1211 Transition.cs @@ -0,0 +1,382 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using ECommons; +using ECommons.Configuration; +using ECommons.DalamudServices; +using ECommons.Hooks.ActionEffectTypes; +using ECommons.ImGuiMethods; +using ECommons.Logging; +using ECommons.MathHelpers; +using ImGuiNET; +using Splatoon; +using Splatoon.Memory; +using Splatoon.SplatoonScripting; + +namespace SplatoonScriptsOfficial.Duties.Shadowbringers; + +public class TEA_P2_1211_Transition : SplatoonScript +{ + private const uint HawkBlastActionEffectId = 18480; + + // May be an array, but use a dictionary to highlight numbers. + private readonly Dictionary _baitPositions = new() + { + { 1, new Vector2(120, 100) }, + { 2, new Vector2(110, 100) }, + { 3, new Vector2(100, 120) }, + { 4, new Vector2(100, 110) }, + { 5, new Vector2(85, 113) }, + { 6, new Vector2(90, 108) }, + { 7, new Vector2(80, 100) }, + { 8, new Vector2(85, 105) } + }; + + private readonly Vector2[] _flarePositions = + [ + new Vector2(90, 90), + new Vector2(100, 86), + new Vector2(110, 90), + new Vector2(114, 100), + new Vector2(110, 110), + new Vector2(100, 114), + new Vector2(90, 110), + new Vector2(86, 100) + ]; + + private readonly Vector2[] _safePositions = + [ + new Vector2(108, 90), + new Vector2(110, 98), + new Vector2(108, 106), + new Vector2(102, 112), + new Vector2(90, 110), + new Vector2(85, 102), + new Vector2(88, 90) + ]; + + private HawkBlastDirection _firstBlastDirection = HawkBlastDirection.None; + + private int _hawkBlastCount; + private bool _mechanicActive; + + private int _myNumber; + + public override HashSet ValidTerritories => [887]; + public override Metadata Metadata => new(3, "Garume"); + + private Config C => Controller.GetConfig(); + + public override void OnSetup() + { + for (var i = 1; i <= 8; i++) + { + var bait = new Element(0) + { + Enabled = false, + overlayText = i.ToString(), + color = 0xFFFF0000 + }; + Controller.RegisterElement($"Bait{i}", bait); + } + + for (var i = 1; i <= 7; i++) + { + var safe = new Element(0) + { + Enabled = false, + overlayText = $"Safe {i}", + radius = 1.5f, + color = 0xFF00FF00 + }; + Controller.RegisterElement($"Safe{i}", safe); + } + + var fa = new Element(0) + { + Enabled = false, + radius = 10f, + Filled = true + }; + Controller.RegisterElement("Flare_a", fa, true); + + var fb = new Element(0) + { + Enabled = false, + radius = 10f, + Filled = true + }; + Controller.RegisterElement("Flare_b", fb, true); + + var fm = new Element(0) + { + Enabled = false, + offX = 100f, + offY = 100f, + radius = 10f, + Filled = true + }; + Controller.RegisterElement("Flare_m", fm, true); + } + + public override void OnVFXSpawn(uint target, string vfxPath) + { + if (vfxPath.StartsWith("vfx/lockon/eff/m0361trg_a")) + { + if (AttachedInfo.VFXInfos.TryGetValue(Svc.ClientState.LocalPlayer.Address, out var info)) + if (info.OrderBy(x => x.Value.Age) + .TryGetFirst(x => x.Key.StartsWith("vfx/lockon/eff/m0361trg_a"), out var effect)) + _myNumber = int.Parse(effect.Key.Replace("vfx/lockon/eff/m0361trg_a", "")[0].ToString()); + + _mechanicActive = true; + } + } + + public override void OnActionEffectEvent(ActionEffectSet set) + { + if (!_mechanicActive || set.Action.RowId != HawkBlastActionEffectId) return; + + _hawkBlastCount++; + if (_myNumber == 0 || _hawkBlastCount >= 19) return; + if (_firstBlastDirection == HawkBlastDirection.None) + { + if (Vector2.Distance(set.Position.ToVector2(), new Vector2(100f, 85f)) < 5f) + _firstBlastDirection = HawkBlastDirection.North; + else if (Vector2.Distance(set.Position.ToVector2(), new Vector2(110f, 90f)) < 5f) + _firstBlastDirection = HawkBlastDirection.Northeast; + else if (Vector2.Distance(set.Position.ToVector2(), new Vector2(115f, 100f)) < 5f) + _firstBlastDirection = HawkBlastDirection.East; + else if (Vector2.Distance(set.Position.ToVector2(), new Vector2(110f, 110f)) < 5f) + _firstBlastDirection = HawkBlastDirection.Southeast; + else + return; + + PluginLog.Log( + $"Blast Position: {set.Position.X} {set.Position.Y} {set.Position.Z} First Blast Direction: {_firstBlastDirection} for MyNumber: {_myNumber}"); + } + + Controller.GetRegisteredElements().Where(x => !x.Key.StartsWith("Flare")).Each(x => x.Value.Enabled = false); + + for (var i = 0; i < 8; i++) + { + var bait = Controller.GetElementByName($"Bait{i + 1}"); + bait!.Enabled = true; + bait.tether = false; + RotatedElement(ref bait, _baitPositions[i + 1], _firstBlastDirection); + if (i + 1 == _myNumber) + { + bait.overlayText = C.BaitMessage; + bait.overlayFScale = 2f; + } + else + { + bait.overlayText = (i + 1).ToString(); + bait.overlayFScale = 1f; + } + } + + for (var i = 0; i < 7; i++) + { + var safe = Controller.GetElementByName($"Safe{i + 1}"); + safe!.Enabled = true; + safe.tether = false; + RotatedElement(ref safe, _safePositions[i], _firstBlastDirection); + } + + // display safe and bait positions + switch (_hawkBlastCount) + { + // To Safe 1 + case 1 or 2: + EnableTetherElement("Safe1", "<1>"); + break; + // To Safe 2 + case 3 or 4: + EnableTetherElement("Safe2", "<1>"); + break; + // To Safe 3 and Bait 1 + case 5 or 6 when _myNumber == 1: + EnableTetherElement("Bait1", "<1>"); + break; + case 5 or 6 when _myNumber == 2: + EnableTetherElement("Bait2", "<1>"); + break; + case 5 or 6: + EnableTetherElement("Safe3", "<1>"); + break; + // To Safe 4 + case 7 or 8: + EnableTetherElement("Safe4", "<1>"); + break; + // To Safe 4 and Bait 2 + case 9 or 10 when _myNumber == 3: + EnableTetherElement("Bait3", "<1>"); + break; + case 9 or 10 when _myNumber == 4: + EnableTetherElement("Bait4", "<1>"); + break; + case 9 or 10: + EnableTetherElement("Safe4", "<1>"); + break; + // To Safe 5 + case 11 or 12: + EnableTetherElement("Safe5", "<1>"); + break; + // To Safe 6 and Bait 3 + case 13 or 14 when _myNumber == 5: + EnableTetherElement("Bait6", "<1>"); + break; + case 13 or 14 when _myNumber == 6: + EnableTetherElement("Bait6", "<1>"); + break; + case 13 or 14: + EnableTetherElement("Safe6", "<1>"); + break; + // To Safe 7 and Bait 4 + case 15 or 16 or 17 when _myNumber == 7: + EnableTetherElement("Bait7", "<1>"); + break; + case 15 or 16 or 17 when _myNumber == 8: + EnableTetherElement("Bait8", "<1>"); + break; + case 15 or 16 or 17: + EnableTetherElement("Safe7", "<1>"); + break; + } + + if (C.ShoulDisplayFlares) + switch (_hawkBlastCount) + { + // display flares + case 1: + case 3: + case 5: + case 10: + case 12: + case 14: + SetNextFlareElement("Flare_a", set.Position.ToVector2()); + break; + case 2: + case 4: + case 6: + case 11: + case 13: + case 15: + SetNextFlareElement("Flare_b", set.Position.ToVector2()); + break; + case 7: + case 16: + SetNextFlareElement("Flare_a", set.Position.ToVector2(), false); + break; + case 8: + case 17: + { + SetNextFlareElement("Flare_b", set.Position.ToVector2(), false); + var flareM = Controller.GetElementByName("Flare_m"); + flareM!.Enabled = true; + break; + } + case 9: + { + var flareM = Controller.GetElementByName("Flare_m"); + flareM!.Enabled = false; + var flareA = Controller.GetElementByName("Flare_a"); + flareA!.Enabled = true; + var flareB = Controller.GetElementByName("Flare_b"); + flareB!.Enabled = true; + break; + } + } + + if (_hawkBlastCount >= 18) Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + } + + public override void OnReset() + { + Reset(); + } + + private void Reset() + { + _mechanicActive = false; + _myNumber = 0; + _hawkBlastCount = 0; + _firstBlastDirection = HawkBlastDirection.None; + Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + } + + private void RotatedElement(ref Element element, Vector2 position, HawkBlastDirection direction) + { + var rotationDegree = direction switch + { + HawkBlastDirection.North => -45, + HawkBlastDirection.Northeast => 0, + HawkBlastDirection.East => 45, + HawkBlastDirection.Southeast => -90, + _ => 0 + }; + + var center = new Vector2(100f, 100f); + var x = position.X - center.X; + var y = position.Y - center.Y; + var x2 = x * Math.Cos(rotationDegree * Math.PI / 180) - y * Math.Sin(rotationDegree * Math.PI / 180); + var y2 = x * Math.Sin(rotationDegree * Math.PI / 180) + y * Math.Cos(rotationDegree * Math.PI / 180); + element.offX = (float)(x2 + center.X); + element.offY = (float)(y2 + center.Y); + } + + private void EnableTetherElement(string elementName, string actorPlaceholder) + { + if (Controller.TryGetElementByName(elementName, out var element)) + { + element.Enabled = true; + element.tether = true; + element.refActorPlaceholder = [actorPlaceholder]; + } + } + + + private void SetNextFlareElement(string elementName, Vector2 current, bool enabled = true) + { + if (Controller.TryGetElementByName(elementName, out var element)) + { + element.Enabled = enabled; + var nextFlare = new Vector2(100f, 100f); + for (var i = 0; i < _flarePositions.Length; i++) + { + if (!(Vector2.Distance(current, _flarePositions[i]) < 5f)) continue; + nextFlare = _flarePositions[(i + 1) % _flarePositions.Length]; + break; + } + + element.offX = nextFlare.X; + element.offY = nextFlare.Y; + } + } + + public override void OnSettingsDraw() + { + ImGui.Text("Bait Message"); + ImGuiEx.HelpMarker("The message that will be displayed on your bait.\nあなたの番号の立ち位置に表示されるメッセージ。"); + ImGui.InputText("", ref C.BaitMessage, 10); + ImGui.Text("Display Flares"); + ImGuiEx.HelpMarker("Display flares on the ground to indicate the next safe spot.\n次の安全地帯を示すために地面にフレアを表示します。"); + ImGui.Checkbox("", ref C.ShoulDisplayFlares); + } + + private enum HawkBlastDirection : byte + { + None, + North, + Northeast, + East, + Southeast + } + + private class Config : IEzConfig + { + public string BaitMessage = "ここで外を向け!"; + public bool ShoulDisplayFlares = true; + } +} \ No newline at end of file diff --git a/SplatoonScripts/Duties/Shadowbringers/TEA P2 Nisi.cs b/SplatoonScripts/Duties/Shadowbringers/TEA P2 Nisi.cs new file mode 100644 index 00000000..d3416b7e --- /dev/null +++ b/SplatoonScripts/Duties/Shadowbringers/TEA P2 Nisi.cs @@ -0,0 +1,209 @@ +using System.Collections.Generic; +using System.Linq; +using Dalamud.Game.ClientState.Objects.Types; +using ECommons; +using ECommons.Configuration; +using ECommons.DalamudServices; +using ECommons.GameHelpers; +using ImGuiNET; +using Splatoon; +using Splatoon.SplatoonScripting; + +namespace SplatoonScriptsOfficial.Duties.Shadowbringers; + +public class TEA_P2_Nisi : SplatoonScript +{ + private const uint AlphaNisiId = 2222; + private const uint AlphaNisiId2 = 2224; + private const uint BetaNisiId = 2223; + private const uint BetaNisiId2 = 2225; + private const uint DeltaNisiId = 2138; + private const uint DeltaNisiId2 = 2140; + private const uint GammaNisiId = 2137; + private const uint GammaNisiId2 = 2139; + + private const uint JudgmentNisiCastId = 18494; + private const uint OpeningLastJudgmentCastId = 18491; + + private const uint JusticeId = 9216; + + private readonly Dictionary> _firstNisiPlayerPairs = new(); + + private bool _isJudgmentNisi; + private bool _isOpeningLastJudgment; + + private uint _myFirstNisiId; + + private static IEnumerable NisiIds => new[] { AlphaNisiId, BetaNisiId, GammaNisiId, DeltaNisiId }; + private static IEnumerable NisiIds2 => new[] { AlphaNisiId2, BetaNisiId2, GammaNisiId2, DeltaNisiId2 }; + + public override HashSet ValidTerritories => [887]; + public override Metadata Metadata => new(2, "Garume"); + + private IBattleNpc? Justice => + Svc.Objects.OfType().FirstOrDefault(x => x.NameId == JusticeId && x.IsTargetable); + + private Config C => Controller.GetConfig(); + + public override void OnSetup() + { + foreach (var nisiId in NisiIds) _firstNisiPlayerPairs[nisiId] = []; + + var nisiPassElement = new Element(0) + { + overlayText = "交換対象", + tether = true + }; + Controller.RegisterElement("NisiPass", nisiPassElement); + } + + public override void OnUpdate() + { + Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + + var justice = Justice; + if (justice == null) + { + _isJudgmentNisi = false; + _isOpeningLastJudgment = false; + return; + } + + if (justice is { IsCasting: true, CastActionId: JudgmentNisiCastId }) + { + _isJudgmentNisi = true; + _isOpeningLastJudgment = false; + } + + if (justice is { IsCasting: true, CastActionId: OpeningLastJudgmentCastId }) + { + _isJudgmentNisi = false; + _isOpeningLastJudgment = true; + } + + if (_isJudgmentNisi && !_isOpeningLastJudgment && !justice.IsCasting) + { + var nisiPlayers = Svc.Objects.OfType() + .Where(x => x.StatusList.Any(y => NisiIds.Contains(y.StatusId))) + .ToArray(); + + if (_firstNisiPlayerPairs.First().Value.Count == 0) + { + if (nisiPlayers.Length == 8) + foreach (var player in nisiPlayers) + { + var nisi = player.StatusList.First(x => NisiIds.Contains(x.StatusId)); + _firstNisiPlayerPairs[nisi.StatusId].Add(player.EntityId); + if (player == Player.Object) + _myFirstNisiId = nisi.StatusId; + } + } + else + { + var myNisi = Player.Object.StatusList.FirstOrDefault(x => NisiIds.Contains(x.StatusId)); + var anotherPlayer = + _firstNisiPlayerPairs[_myFirstNisiId].First(x => x != Player.Object.EntityId) + .GetObject() as IBattleChara; + var anotherPlayerNisi = anotherPlayer.StatusList.FirstOrDefault(x => NisiIds.Contains(x.StatusId)); + + //2nd Nisi + if (myNisi == null && anotherPlayerNisi != null && anotherPlayerNisi.RemainingTime < C.FirstNisiTime) + SetPositionElement(anotherPlayer, "NisiPass"); + + //2nd Nisi + if (anotherPlayerNisi == null && myNisi != null && myNisi.RemainingTime < C.FirstNisiTime) + SetPositionElement(anotherPlayer, "NisiPass"); + } + } + + if (_isOpeningLastJudgment && !justice.IsCasting) + { + var myMisi = Player.Object.StatusList.FirstOrDefault(x => NisiIds.Contains(x.StatusId)); + var myNisi2 = Player.Object.StatusList.FirstOrDefault(x => NisiIds2.Contains(x.StatusId)); + + // 3rd Nisi + if (myMisi != null && myMisi.RemainingTime < C.SecondNisiTime) + { + var matchingNisiId = GetMatchingNisiId(myMisi.StatusId); + var player = Svc.Objects.OfType() + .Where(x => !x.StatusList.Any(y => NisiIds.Contains(y.StatusId))) + .FirstOrDefault(x => x.StatusList.Any(y => y.StatusId == matchingNisiId)); + + SetPositionElement(player, "NisiPass"); + } + + // 3rd Nisi or 4th Nisi + if (myMisi == null && myNisi2 != null) + { + var matchingNisiId = GetMatchingNisiId(myNisi2.StatusId); + var player = Svc.Objects.OfType().FirstOrDefault(x => + x.StatusList.Any(y => y.StatusId == matchingNisiId && y.RemainingTime < C.SecondNisiTime)); + SetPositionElement(player, "NisiPass"); + } + + // 4th Nisi + // probably not needed + if (myMisi != null && myNisi2 != null && myMisi.RemainingTime < C.SecondNisiTime) + { + var matchingNisiId = GetMatchingNisiId(myMisi.StatusId); + var player = Svc.Objects.OfType() + .Where(x => !x.StatusList.Any(y => NisiIds.Contains(y.StatusId))) + .FirstOrDefault(x => x.StatusList.Any(y => y.StatusId == matchingNisiId)); + SetPositionElement(player, "NisiPass"); + } + } + } + + public override void OnReset() + { + _firstNisiPlayerPairs.Each(x => x.Value.Clear()); + _isJudgmentNisi = false; + _isOpeningLastJudgment = false; + } + + private uint GetMatchingNisiId(uint nisiId) + { + return nisiId switch + { + AlphaNisiId => AlphaNisiId2, + BetaNisiId => BetaNisiId2, + GammaNisiId => GammaNisiId2, + DeltaNisiId => DeltaNisiId2, + AlphaNisiId2 => AlphaNisiId, + BetaNisiId2 => BetaNisiId, + GammaNisiId2 => GammaNisiId, + DeltaNisiId2 => DeltaNisiId, + _ => 0 + }; + } + + private void SetPositionElement(IGameObject? player, string elementName) + { + if (player == null) return; + if (Controller.TryGetElementByName(elementName, out var element)) + { + element.Enabled = true; + element.refX = player.Position.X; + element.refY = player.Position.Z; + } + } + + public override void OnSettingsDraw() + { + ImGui.Text("1 ~ 2 Nisi"); + foreach (var pair in _firstNisiPlayerPairs) ImGui.Text($"{pair.Key.GetObject()}: {string.Join(", ", pair.Value)}"); + ImGui.Spacing(); + ImGui.Text("Current Nisi"); + foreach (var nisi in Svc.Objects.OfType() + .SelectMany(x => x.StatusList) + .Where(x => NisiIds.Contains(x.StatusId)) + ) + ImGui.Text($"{nisi.StatusId}: {nisi.RemainingTime}"); + } + + private class Config : IEzConfig + { + public float FirstNisiTime { get; } = 8f; + public float SecondNisiTime { get; } = 15f; + } +} \ No newline at end of file diff --git a/SplatoonScripts/Duties/Shadowbringers/TEA P2 Temporal Stasis.cs b/SplatoonScripts/Duties/Shadowbringers/TEA P2 Temporal Stasis.cs new file mode 100644 index 00000000..fecb6bd9 --- /dev/null +++ b/SplatoonScripts/Duties/Shadowbringers/TEA P2 Temporal Stasis.cs @@ -0,0 +1,159 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Statuses; +using ECommons; +using ECommons.Configuration; +using ECommons.DalamudServices; +using ECommons.GameHelpers; +using ECommons.ImGuiMethods; +using Splatoon; +using Splatoon.SplatoonScripting; + +namespace SplatoonScriptsOfficial.Duties.Shadowbringers; + +public class TEA_P2_Temporal_Stasis : SplatoonScript +{ + public enum BaitType + { + West, + East, + NorthLeftBossSide, + SouthLeftBossSide, + NorthRightBossSide, + SouthRightBossSide, + JusticeSide + } + + private enum CruiseChaserSide + { + East, + West + } + + private const uint LightningDebuffId = 1121; + private const uint RedTetherDebuffId = 1123; + private const uint BlueTetherDebuffId = 1124; + private bool _isStartTemporalStasis; + private bool _shouldDisplayElement; + public override HashSet? ValidTerritories => [887]; + public override Metadata? Metadata => new(1, "Garume"); + private IBattleNpc? CruiseChaser => Svc.Objects.OfType().FirstOrDefault(x => x.DataId == 0x2C4E); + + private Config C => Controller.GetConfig(); + + public override void OnSetup() + { + var element = new Element(0) + { + radius = 0.35f, + color = 0xffffffff, + overlayText = "Go Here", + overlayVOffset = 2f, + overlayFScale = 2f, + tether = true + }; + + Controller.RegisterElement("TEA_P2_Temporal_Stasis_Bait", element, true); + } + + public override void OnMessage(string message) + { + if (message.Contains("我はアレキサンダー……機械仕掛けの神なり……。")) _isStartTemporalStasis = true; + } + + public override void OnTetherCreate(uint source, uint target, uint data2, uint data3, uint data5) + { + if (_isStartTemporalStasis) _shouldDisplayElement = true; + } + + public override void OnTetherRemoval(uint source, uint data2, uint data3, uint data5) + { + if (_isStartTemporalStasis) _isStartTemporalStasis = false; + } + + public override void OnUpdate() + { + Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + + var cruiseChaser = CruiseChaser; + if (cruiseChaser == null || !_isStartTemporalStasis) return; + + var statuses = Player.Status; + var baitType = GetBaitType(statuses); + var cruiseChaserSide = GetCruiseChaserSide(cruiseChaser); + var baitPosition = GetBaitPosition(baitType, cruiseChaserSide); + if (Controller.TryGetElementByName("TEA_P2_Temporal_Stasis_Bait", out var element)) + { + element.Enabled = _shouldDisplayElement; + element.refX = baitPosition.X; + element.refY = baitPosition.Y; + } + } + + public override void OnReset() + { + _isStartTemporalStasis = false; + _shouldDisplayElement = false; + } + + private BaitType GetBaitType(IEnumerable statuses) + { + foreach (var status in statuses) + switch (status.StatusId) + { + case LightningDebuffId: + return C.LightningBaitPosition; + case RedTetherDebuffId: + return C.RedTetherBaitPosition; + case BlueTetherDebuffId: + return C.BlueTetherBaitPosition; + } + + return C.NothingBaitPosition; + } + + private CruiseChaserSide GetCruiseChaserSide(IGameObject cruiseChaser) + { + return cruiseChaser.Position.X > 100f ? CruiseChaserSide.East : CruiseChaserSide.West; + } + + private Vector2 GetBaitPosition(BaitType baitType, CruiseChaserSide cruiseChaserSide) + { + return (baitType, cruiseChaserSide) switch + { + (BaitType.NorthRightBossSide, _) => new Vector2(106f, 98f), + (BaitType.SouthRightBossSide, _) => new Vector2(106f, 102f), + (BaitType.NorthLeftBossSide, _) => new Vector2(94f, 98f), + (BaitType.SouthLeftBossSide, _) => new Vector2(94f, 102f), + (BaitType.East, CruiseChaserSide.East) => new Vector2(113f, 100f), + (BaitType.East, CruiseChaserSide.West) => new Vector2(118f, 100f), + (BaitType.West, CruiseChaserSide.East) => new Vector2(82f, 100f), + (BaitType.West, CruiseChaserSide.West) => new Vector2(87f, 100f), + (BaitType.JusticeSide, CruiseChaserSide.East) => new Vector2(82f, 100f), + (BaitType.JusticeSide, CruiseChaserSide.West) => new Vector2(118f, 100f), + _ => Vector2.Zero + }; + } + + public override void OnSettingsDraw() + { + ImGuiEx.Text("Blue Tether Bait Position"); + ImGuiEx.EnumCombo("##BlueTetherBaitPosition", ref C.BlueTetherBaitPosition); + ImGuiEx.Text("Red Tether Bait Position"); + ImGuiEx.EnumCombo("##RedTetherBaitPosition", ref C.RedTetherBaitPosition); + ImGuiEx.Text("Lightning Bait Position"); + ImGuiEx.EnumCombo("##LightningBaitPosition", ref C.LightningBaitPosition); + ImGuiEx.Text("Nothing Bait Position"); + ImGuiEx.EnumCombo("##NothingBaitPosition", ref C.NothingBaitPosition); + } + + private class Config : IEzConfig + { + public BaitType BlueTetherBaitPosition; + public BaitType LightningBaitPosition; + public BaitType NothingBaitPosition; + public BaitType RedTetherBaitPosition; + } +} \ No newline at end of file diff --git a/SplatoonScripts/Duties/Shadowbringers/TEA P3 Wormhole Formation.cs b/SplatoonScripts/Duties/Shadowbringers/TEA P3 Wormhole Formation.cs new file mode 100644 index 00000000..10c1ff25 --- /dev/null +++ b/SplatoonScripts/Duties/Shadowbringers/TEA P3 Wormhole Formation.cs @@ -0,0 +1,200 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface.Components; +using ECommons; +using ECommons.Configuration; +using ECommons.DalamudServices; +using ECommons.ImGuiMethods; +using ECommons.MathHelpers; +using ECommons.Schedulers; +using ImGuiNET; +using Splatoon; +using Splatoon.Memory; +using Splatoon.SplatoonScripting; + +namespace SplatoonScriptsOfficial.Duties.Shadowbringers; + +public class TEA_P3_Wormhole_Formation : SplatoonScript +{ + private const uint ChakramCastId = 18517; + private static readonly uint[] WormholeDataIds = [2007519, 2007520, 2007521]; + + private readonly Dictionary _baitPositions = new() + { + { 1, [new Vector2(86.5f, 86f), new Vector2(86.5f, 86f), new Vector2(81f, 99f), new Vector2(90f, 97f)] }, + { 2, [new Vector2(113.5f, 86f), new Vector2(113.5f, 86f), new Vector2(119f, 99f), new Vector2(110f, 103f)] }, + { 3, [new Vector2(87, 113), new Vector2(87f, 113f), new Vector2(81f, 101f), new Vector2(81f, 101f)] }, + { 4, [new Vector2(113, 113), new Vector2(113f, 113f), new Vector2(119f, 101f), new Vector2(119f, 101f)] }, + { 5, [new Vector2(84.45f, 89.65f), new Vector2(82f, 96f), new Vector2(86.5f, 86f), new Vector2(81f, 99f)] }, + { 6, [new Vector2(115.55f, 89.65f), new Vector2(118, 104f), new Vector2(113.5f, 86f), new Vector2(119f, 99f)] }, + { 7, [new Vector2(83, 93), new Vector2(81.5f, 100f), new Vector2(85f, 93f), new Vector2(86.5f, 114f)] }, + { 8, [new Vector2(117, 93), new Vector2(118.5f, 100f), new Vector2(115f, 107f), new Vector2(113.5f, 114f)] } + }; + + private readonly List> _invertApplyIndex = + [ + [], + [5, 6], + [7, 8], + [1, 2] + ]; + + private TickScheduler? _chakramScheduler; + private int _currentPhase; + private bool _isStartWormholeFormation; + private int _myNumber; + private bool _shouldInvert; + private int _wormholeChangedCount; + + public override HashSet? ValidTerritories => [887]; + public override Metadata? Metadata => new(3, "Garume"); + + private Config C => Controller.GetConfig(); + + public override void OnSetup() + { + for (var i = 1; i <= 8; i++) + { + var element = new Element(0) + { + radius = 0.35f, + overlayVOffset = 2f, + overlayFScale = 2f + }; + + Controller.RegisterElement($"Bait{i}", element, true); + } + } + + public override void OnMessage(string message) + { + if (message.Contains("アレキサンダー・プライムの「次元断絶のマーチ」")) _isStartWormholeFormation = true; + } + + public override void OnStartingCast(uint source, uint castId) + { + if (!_isStartWormholeFormation) return; + if (castId == ChakramCastId) _chakramScheduler ??= new TickScheduler(() => _currentPhase = 1, 5700); + } + + public override void OnVFXSpawn(uint target, string vfxPath) + { + if (!vfxPath.StartsWith("vfx/lockon/eff/m0361trg_a")) return; + if (!AttachedInfo.VFXInfos.TryGetValue(Svc.ClientState.LocalPlayer.Address, out var info)) return; + if (info.OrderBy(x => x.Value.Age) + .TryGetFirst(x => x.Key.StartsWith("vfx/lockon/eff/m0361trg_a"), out var effect)) + _myNumber = int.Parse(effect.Key.Replace("vfx/lockon/eff/m0361trg_a", "")[0].ToString()); + } + + public override void OnObjectEffect(uint target, ushort data1, ushort data2) + { + var targetObject = target.GetObject(); + // PluginLog.Warning($"Name:{targetObject.Name} DataID: {targetObject.DataId} Data1: {data1}, Data2: {data2}"); + if (WormholeDataIds.All(x => x != targetObject?.DataId)) return; + if (data1 == 4 && data2 == 8 && _wormholeChangedCount > 5) + { + _isStartWormholeFormation = false; + return; + } + + if (data1 != 1 && data2 != 2) return; + var wormholePosition = targetObject.Position.ToVector2(); + if (wormholePosition is { X: > 100, Y: < 100 } or { X: < 100, Y: > 100 }) _shouldInvert = true; + _wormholeChangedCount++; + if (_wormholeChangedCount is 3 or 5 or 7) _currentPhase++; + } + + public override void OnReset() + { + _isStartWormholeFormation = false; + _myNumber = 0; + _shouldInvert = false; + _currentPhase = 0; + _wormholeChangedCount = 0; + _chakramScheduler?.Dispose(); + _chakramScheduler = null; + } + + public override void OnUpdate() + { + if (!_isStartWormholeFormation) + { + Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + return; + } + + for (var i = 1; i <= 8; i++) + if (Controller.TryGetElementByName($"Bait{i}", out var element)) + { + var position = _baitPositions[i][_currentPhase]; + var number = i; + if (_shouldInvert && _invertApplyIndex[_currentPhase].Contains(i)) + { + position = position with { X = 200 - position.X }; + number = number % 2 == 0 ? number - 1 : number + 1; + } + + element.SetOffPosition(position.ToVector3()); + element.Enabled = true; + if (number == _myNumber) + { + element.overlayText = C.BaitText; + element.color = GradientColor.Get(C.BaitColor1, C.BaitColor2).ToUint(); + element.tether = true; + element.thicc = 10f; + } + else if (C.ShouldDisplayOtherBait) + { + element.overlayText = number.ToString(); + element.color = C.OtherBaitColor.ToUint(); + element.tether = false; + element.thicc = 2f; + } + else + { + element.Enabled = false; + } + } + } + + public override void OnSettingsDraw() + { + if (ImGui.CollapsingHeader("My Bait Settings:")) + { + ImGui.Indent(); + ImGui.Text("Bait Text:"); + ImGui.InputText("", ref C.BaitText, 100); + ImGui.Text("Bait Color:"); + ImGuiComponents.HelpMarker( + "Change the color of the bait and the text that will be displayed on your bait.\nSetting different values makes it rainbow."); + ImGui.Indent(); + ImGui.ColorEdit4("Color 1", ref C.BaitColor1, ImGuiColorEditFlags.NoInputs); + ImGui.SameLine(); + ImGui.ColorEdit4("Color 2", ref C.BaitColor2, ImGuiColorEditFlags.NoInputs); + ImGui.Unindent(); + ImGui.Unindent(); + } + + if (ImGui.CollapsingHeader("Other Bait Settings:")) + { + ImGui.Indent(); + ImGui.Checkbox("Display Other Bait", ref C.ShouldDisplayOtherBait); + ImGui.Text("Other Bait Color:"); + ImGuiComponents.HelpMarker("Change the color of the bait that will be displayed on other bait."); + ImGui.Indent(); + ImGui.ColorEdit4("Color", ref C.OtherBaitColor, ImGuiColorEditFlags.NoInputs); + ImGui.Unindent(); + ImGui.Unindent(); + } + } + + public class Config : IEzConfig + { + public Vector4 BaitColor1 = 0xFFFF00FF.ToVector4(); + public Vector4 BaitColor2 = 0xFFFFFF00.ToVector4(); + public string BaitText = "Go Here"; + public Vector4 OtherBaitColor = 0xFFFF0000.ToVector4(); + public bool ShouldDisplayOtherBait = true; + } +} \ No newline at end of file diff --git "a/SplatoonScripts/Duties/Shadowbringers/TEA P4 Fate Projection \316\261.cs" "b/SplatoonScripts/Duties/Shadowbringers/TEA P4 Fate Projection \316\261.cs" new file mode 100644 index 00000000..a24fd2f2 --- /dev/null +++ "b/SplatoonScripts/Duties/Shadowbringers/TEA P4 Fate Projection \316\261.cs" @@ -0,0 +1,266 @@ +using System.Collections.Generic; +using System.Linq; +using Dalamud.Game.ClientState.Objects.Types; +using ECommons; +using ECommons.DalamudServices; +using ECommons.GameHelpers; +using ECommons.Hooks.ActionEffectTypes; +using ECommons.ImGuiMethods; +using ECommons.Logging; +using Splatoon; +using Splatoon.SplatoonScripting; +using Vector3 = System.Numerics.Vector3; + +namespace SplatoonScriptsOfficial.Duties.Shadowbringers; + +public class TEA_P4_Fate_Projection_α : SplatoonScript +{ + private readonly List _futurePlayers = []; + + private FutureActionType[] _futureActionTypes = + [ + FutureActionType.None, + FutureActionType.None, + FutureActionType.None + ]; + + private bool _isOpenSafeSpot; + + private bool _isStartFateProjectionCasting; + private uint? _myFuturePlayer; + + private IBattleChara? SafeAlexander => Svc.Objects.OfType() + .FirstOrDefault(x => x is { NameId: 0x2352, IsCasting: true, CastActionId: 18858 }); + + public override HashSet? ValidTerritories => [887]; + public override Metadata? Metadata => new(1, "Garume"); + + + private string GetFutureActionText(FutureActionType type) + { + return type switch + { + FutureActionType.FirstMotion => "最初は動け!", + FutureActionType.FirstStillness => "最初は動くな", + FutureActionType.SecondMotion => "最後は動け!", + FutureActionType.SecondStillness => "最後は動くな!", + FutureActionType.Defamation => "名誉罰: 上へ", + FutureActionType.SharedSentence => "集団罰: 左下へ", + FutureActionType.Aggravated => "加重罰: 右下へ", + FutureActionType.Nothing => "無職: 左下へ", + FutureActionType.UnKnown => "UnKnown: 左下へ?", + _ => "None: 左下へ?" + }; + } + + + public override void OnStartingCast(uint source, uint castId) + { + if (castId == 18555) + { + _isStartFateProjectionCasting = true; + Controller.Schedule(() => + { + if (Controller.TryGetElementByName("FirstText", out var firstTextElement)) + firstTextElement.overlayTextColor = EColor.Red.ToUint(); + }, 34 * 1000); + Controller.Schedule(() => + { + if (Controller.TryGetElementByName("FirstText", out var firstTextElement)) + firstTextElement.overlayTextColor = EColor.White.ToUint(); + + if (Controller.TryGetElementByName("ThirdText", out var thirdTextElement)) + thirdTextElement.overlayTextColor = EColor.Red.ToUint(); + }, 39 * 1000); + Controller.Schedule(() => + { + if (Controller.TryGetElementByName("ThirdText", out var thirdTextElement)) + thirdTextElement.overlayTextColor = EColor.White.ToUint(); + _isStartFateProjectionCasting = false; + }, 44 * 1000); + } + } + + public override void OnReset() + { + _isStartFateProjectionCasting = false; + _futureActionTypes = + [ + FutureActionType.None, + FutureActionType.None, + FutureActionType.None + ]; + _futurePlayers.Clear(); + _myFuturePlayer = null; + _isOpenSafeSpot = false; + } + + public override void OnSetup() + { + var firstTextElement = new Element(0) + { + overlayText = "", + overlayVOffset = 8f, + overlayFScale = 5f, + Filled = false, + radius = 0f + }; + firstTextElement.SetOffPosition(new Vector3(100f, 0, 100f)); + Controller.RegisterElement("FirstText", firstTextElement, true); + + var secondTextElement = new Element(0) + { + overlayText = "", + overlayVOffset = 5f, + overlayFScale = 5f, + Filled = false, + radius = 0f + }; + secondTextElement.SetOffPosition(new Vector3(100f, 0, 100f)); + Controller.RegisterElement("SecondText", secondTextElement, true); + + var thirdTextElement = new Element(0) + { + overlayText = "", + overlayVOffset = 2f, + overlayFScale = 5f, + Filled = false, + radius = 0f + }; + thirdTextElement.SetOffPosition(new Vector3(100f, 0, 100f)); + Controller.RegisterElement("ThirdText", thirdTextElement, true); + + Controller.RegisterElementFromCode("NorthBait", + "{\"Name\":\"北側サークル\",\"type\":1,\"offY\":7.0,\"radius\":3.0,\"color\":3372169472,\"fillIntensity\":0.0,\"thicc\":5.0,\"refActorNPCNameID\":9042,\"refActorRequireCast\":true,\"refActorCastId\":[18858],\"refActorUseCastTime\":true,\"refActorCastTimeMax\":30.0,\"refActorUseOvercast\":true,\"refActorComparisonType\":6,\"includeRotation\":true,\"onlyUnTargetable\":true,\"onlyVisible\":true,\"refActorTetherTimeMin\":0.0,\"refActorTetherTimeMax\":0.0}"); + Controller.RegisterElementFromCode("SouthEastBait", + "{\"Name\":\"南側サークル1\",\"type\":1,\"offX\":-2.5,\"offY\":41.5,\"radius\":1.0,\"color\":3372169472,\"fillIntensity\":0.0,\"thicc\":5.0,\"refActorNPCNameID\":9042,\"refActorRequireCast\":true,\"refActorCastId\":[18858],\"refActorUseCastTime\":true,\"refActorCastTimeMax\":30.0,\"refActorUseOvercast\":true,\"refActorComparisonType\":6,\"includeRotation\":true,\"onlyUnTargetable\":true,\"onlyVisible\":true,\"refActorTetherTimeMin\":0.0,\"refActorTetherTimeMax\":0.0}"); + Controller.RegisterElementFromCode("SouthWestBait", + "{\"Name\":\"南側サークル2\",\"type\":1,\"offX\":2.5,\"offY\":41.5,\"radius\":1.0,\"color\":3372169472,\"fillIntensity\":0.0,\"thicc\":5.0,\"refActorNPCNameID\":9042,\"refActorRequireCast\":true,\"refActorCastId\":[18858],\"refActorUseCastTime\":true,\"refActorCastTimeMax\":30.0,\"refActorUseOvercast\":true,\"refActorComparisonType\":6,\"includeRotation\":true,\"onlyUnTargetable\":true,\"onlyVisible\":true,\"refActorTetherTimeMin\":0.0,\"refActorTetherTimeMax\":0.0}"); + } + + public override void OnUpdate() + { + if (!_isStartFateProjectionCasting) + { + Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + return; + } + + ApplyTextFromFutureAction("FirstText", _futureActionTypes[0]); + ApplyTextFromFutureAction("SecondText", _futureActionTypes[1]); + ApplyTextFromFutureAction("ThirdText", _futureActionTypes[2]); + + var safeAlexander = SafeAlexander; + if (safeAlexander == null || _isOpenSafeSpot) return; + _isOpenSafeSpot = true; + switch (_futureActionTypes[1]) + { + case FutureActionType.Defamation: + ApplyBaitStyle("NorthBait"); + break; + case FutureActionType.Aggravated: + ApplyBaitStyle("SouthEastBait"); + break; + case FutureActionType.SharedSentence: + case FutureActionType.Nothing: + case FutureActionType.None: + case FutureActionType.FirstMotion: + case FutureActionType.FirstStillness: + case FutureActionType.SecondMotion: + case FutureActionType.SecondStillness: + case FutureActionType.UnKnown: + default: + ApplyBaitStyle("SouthWestBait"); + break; + } + } + + private void ApplyTextFromFutureAction(string elementName, FutureActionType type) + { + if (type == FutureActionType.None) return; + if (!Controller.TryGetElementByName(elementName, out var element)) return; + var text = GetFutureActionText(type); + element.overlayText = text; + element.Enabled = true; + } + + private void ApplyBaitStyle(string elementName) + { + if (!Controller.TryGetElementByName(elementName, out var element)) return; + element.Enabled = true; + element.tether = true; + element.color = GradientColor.Get(0xFFFF00FF.ToVector4(), 0xFFFFFF00.ToVector4()).ToUint(); + element.thicc = 10f; + } + + public override void OnTetherCreate(uint source, uint target, uint data2, uint data3, uint data5) + { + if (!_isStartFateProjectionCasting) return; + _futurePlayers.Add(target); + if (source == Player.Object.EntityId) + { + _myFuturePlayer = target; + Controller.Schedule(() => + { + var reversed = _futurePlayers.ToArray().Reverse().ToArray(); + for (var i = 0; i < reversed.Length; i++) + if (_myFuturePlayer == reversed[i]) + switch (i) + { + case 0: + _futureActionTypes[1] = FutureActionType.SharedSentence; + break; + case 1: + _futureActionTypes[1] = FutureActionType.Defamation; + break; + case 2: + case 3: + case 4: + _futureActionTypes[1] = FutureActionType.Aggravated; + break; + case 5: + case 6: + case 7: + _futureActionTypes[1] = FutureActionType.Nothing; + break; + } + }, 1000); + } + } + + public override void OnActionEffectEvent(ActionEffectSet set) + { + if (!_isStartFateProjectionCasting) return; + if (set is { Action: not null, Source: not null, Target: not null } && set.Target.EntityId == _myFuturePlayer) + { + PluginLog.Warning("ActionId: " + set.Action.RowId); + + var futureAction = set.Action.RowId switch + { + 19213 => FutureActionType.FirstMotion, + 19214 => FutureActionType.FirstStillness, + 18585 => FutureActionType.SecondMotion, + 18586 => FutureActionType.SecondStillness, + 18597 => FutureActionType.SecondStillness, + _ => FutureActionType.None + }; + if (futureAction == FutureActionType.None) return; + if (_futureActionTypes[0] == FutureActionType.None) _futureActionTypes[0] = futureAction; + else _futureActionTypes[2] = futureAction; + } + } + + private enum FutureActionType : byte + { + None, + FirstMotion, + FirstStillness, + SecondMotion, + SecondStillness, + Defamation, + SharedSentence, + Aggravated, + Nothing, + UnKnown + } +} \ No newline at end of file diff --git "a/SplatoonScripts/Duties/Shadowbringers/TEA P4 Fate Projection \316\262.cs" "b/SplatoonScripts/Duties/Shadowbringers/TEA P4 Fate Projection \316\262.cs" new file mode 100644 index 00000000..fc93d3a3 --- /dev/null +++ "b/SplatoonScripts/Duties/Shadowbringers/TEA P4 Fate Projection \316\262.cs" @@ -0,0 +1,257 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using ECommons; +using ECommons.GameHelpers; +using ECommons.Hooks.ActionEffectTypes; +using ECommons.ImGuiMethods; +using ECommons.Logging; +using Splatoon; +using Splatoon.SplatoonScripting; + +namespace SplatoonScriptsOfficial.Duties.Shadowbringers; + +public class TEA_P4_Fate_Projection_β : SplatoonScript +{ + public enum FutureActionType : byte + { + EastUpperLeft, + EastCenterLeft, + EastLowerLeft, + EastCenter, + North, + Spread, + Stack, + None + } + + private readonly List _futurePlayers = []; + + private bool _canAddFuturePlayer = true; + + private bool _isStartFateProjectionCasting; + + private bool _myDefuffIsYellow; + private uint? _myFuturePlayer; + + public override Metadata? Metadata => new(1, "Garume"); + public override HashSet? ValidTerritories => [887]; + + public override void OnStartingCast(uint source, uint castId) + { + if (castId == 19219) + { + _isStartFateProjectionCasting = true; + PluginLog.Warning("Start Fate Projection Casting"); + + Controller.Schedule(() => + { + if (Controller.TryGetElementByName("FirstBait", out var firstElement)) + firstElement.Enabled = false; + if (Controller.TryGetElementByName("FirstText", out var firstTextElement)) + firstTextElement.Enabled = false; + if (Controller.TryGetElementByName("SecondText", out var secondElement)) + secondElement.overlayTextColor = EColor.Red.ToUint(); + }, 1000 * 55); + Controller.Schedule(() => + { + if (Controller.TryGetElementByName("SecondText", out var secondTextElement)) + { + secondTextElement.overlayTextColor = EColor.White.ToUint(); + secondTextElement.Enabled = false; + } + + if (Controller.TryGetElementByName("SecondBait", out var secondElement)) + { + secondElement.Enabled = true; + secondElement.tether = true; + } + }, 1000 * 63); + Controller.Schedule(() => + { + _isStartFateProjectionCasting = false; + if (Controller.TryGetElementByName("SecondBait", out var secondElement)) secondElement.tether = false; + }, 1000 * 70); + } + } + + public override void OnReset() + { + _isStartFateProjectionCasting = false; + _futurePlayers.Clear(); + _myFuturePlayer = null; + _myDefuffIsYellow = false; + _canAddFuturePlayer = true; + Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + } + + public override void OnSetup() + { + var element = new Element(0) + { + color = EColor.Blue.ToUint(), + thicc = 5f + }; + + Controller.RegisterElement("FirstBait", element, true); + + var secondElement = new Element(0) + { + radius = 1f, + color = EColor.Blue.ToUint(), + overlayText = "足元へ!", + overlayFScale = 2f, + overlayVOffset = 2f, + thicc = 5f + }; + + Controller.RegisterElement("SecondBait", secondElement, true); + + var firstTextElement = new Element(0) + { + overlayText = "", + overlayVOffset = 8f, + overlayFScale = 5f, + Filled = false, + radius = 0f + }; + firstTextElement.SetOffPosition(new Vector3(100f, 0, 100f)); + Controller.RegisterElement("FirstText", firstTextElement, true); + + var secondTextElement = new Element(0) + { + overlayText = "", + overlayVOffset = 5f, + overlayFScale = 5f, + Filled = false, + radius = 0f + }; + secondTextElement.SetOffPosition(new Vector3(100f, 0, 100f)); + Controller.RegisterElement("SecondText", secondTextElement, true); + } + + public override void OnUpdate() + { + if (!_isStartFateProjectionCasting) Controller.GetRegisteredElements().Each(x => x.Value.Enabled = false); + } + + private string GetFutureActionText(FutureActionType type) + { + var stackDirection = _myDefuffIsYellow ? "北に" : "南に"; + return type switch + { + FutureActionType.EastCenter => "終了後、外周に行け", + FutureActionType.EastCenterLeft => "終了後、外周に行け", + FutureActionType.EastLowerLeft => "終了後、外周に行け", + FutureActionType.EastUpperLeft => "終了後、外周に行け", + FutureActionType.Spread => "散開", + FutureActionType.Stack => $"頭割り {stackDirection}", + _ => "" + }; + } + + private Vector2 GetFutureActionPosition(FutureActionType type) + { + return type switch + { + FutureActionType.EastUpperLeft => new Vector2(112f, 98.5f), + FutureActionType.EastCenterLeft => new Vector2(112f, 100f), + FutureActionType.EastLowerLeft => new Vector2(112f, 101.5f), + FutureActionType.EastCenter => new Vector2(115, 100), + FutureActionType.North => new Vector2(92, 84f), + FutureActionType.None => Vector2.Zero, + _ => Vector2.Zero + }; + } + + public override void OnActionEffectEvent(ActionEffectSet set) + { + if (!_isStartFateProjectionCasting) return; + if (set.Source is not { DataId: 0x2C55 }) return; + switch (set.Action) + { + case { RowId: 18592 }: + { + var text = GetFutureActionText(FutureActionType.Spread); + if (Controller.TryGetElementByName("SecondText", out var textElement)) + { + textElement.overlayText = text; + textElement.Enabled = true; + } + + break; + } + case { RowId: 18593 }: + { + var text = GetFutureActionText(FutureActionType.Stack); + if (Controller.TryGetElementByName("SecondText", out var textElement)) + { + textElement.overlayText = text; + textElement.Enabled = true; + } + + break; + } + } + + if (set.Action is { RowId: 18590 }) + if (Controller.TryGetElementByName("SecondBait", out var element)) + { + element.SetOffPosition(set.Source.Position); + element.Enabled = true; + } + } + + public override void OnTetherCreate(uint source, uint target, uint data2, uint data3, uint data5) + { + if (!_isStartFateProjectionCasting) return; + + if (_canAddFuturePlayer) _futurePlayers.Add(target); + + + if (source == Player.Object.EntityId) + { + _myFuturePlayer = target; + Controller.Schedule(() => + { + _canAddFuturePlayer = false; + var reversed = _futurePlayers.ToArray().Reverse().ToArray(); + var futureAction = FutureActionType.None; + for (var i = 0; i < reversed.Length; i++) + if (_myFuturePlayer == reversed[i]) + { + futureAction = i switch + { + 0 => FutureActionType.EastCenter, + 1 => FutureActionType.North, + 2 => FutureActionType.EastCenterLeft, + 3 => FutureActionType.EastUpperLeft, + 4 => FutureActionType.EastUpperLeft, + 5 => FutureActionType.EastUpperLeft, + 6 => FutureActionType.EastLowerLeft, + 7 => FutureActionType.EastUpperLeft, + _ => FutureActionType.None + }; + + if (i is 1 or 3 or 5 or 7) + _myDefuffIsYellow = true; + } + + if (Controller.TryGetElementByName("FirstBait", out var element)) + { + var position = GetFutureActionPosition(futureAction); + element.SetOffPosition(new Vector3(position.X, 0, position.Y)); + element.tether = true; + element.Enabled = true; + } + + if (Controller.TryGetElementByName("FirstText", out var textElement)) + { + var text = GetFutureActionText(futureAction); + textElement.overlayText = text; + textElement.Enabled = true; + } + }, 2000); + } + } +} \ No newline at end of file diff --git a/SplatoonScripts/update.csv b/SplatoonScripts/update.csv index 09db635b..abe4cde8 100644 --- a/SplatoonScripts/update.csv +++ b/SplatoonScripts/update.csv @@ -35,6 +35,10 @@ SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Hello_Near_Far_World SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Dynamis_Sigma,7,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Dynamis Sigma.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Program_Loop,4,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Program Loop.cs SplatoonScriptsOfficial.Duties.Endwalker.The_Omega_Protocol@Hello_World_MoveGuide,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Endwalker/The Omega Protocol/Hello World MoveGuide.cs +SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P3_Wormhole_Formation,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P3 Wormhole Formation.cs +SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_1211_Transition,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P2 1211 Transition.cs +SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_Nisi,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P2 Nisi.cs +SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_Temporal_Stasis,1,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA P2 Temporal Stasis.cs SplatoonScriptsOfficial.Duties.Shadowbringers@TEA_P2_Transition,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Shadowbringers/TEA_P2_Transition.cs SplatoonScriptsOfficial.Duties.Dawntrail@EX2_Projection_of_Triumph,3,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/EX2 Projection of Triumph.cs SplatoonScriptsOfficial.Duties.Dawntrail@R1S_Protean_Highlight,2,https://github.com/PunishXIV/Splatoon/raw/main/SplatoonScripts/Duties/Dawntrail/R1S Protean Highlight.cs