Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Endwalker #3

Merged
merged 5 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions BossMod/BossModule/BossModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public class ModuleInfoAttribute : Attribute
public Type? StatusIDType; // default: ns.SID
public Type? TetherIDType; // default: ns.TetherID
public Type? IconIDType; // default: ns.IconID
public string? DisplayName;
public uint ExVersion; // default: 0
public uint QuestID; // default: 0
public uint DynamicEventID; // default: 0
public uint FateID; // default: 0
public uint NotoriousMonsterID; // default: 0
Expand Down
65 changes: 3 additions & 62 deletions BossMod/Config/ConfigUI.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace BossMod
Expand All @@ -30,18 +24,15 @@ public UINode(ConfigNode node)

private List<UINode> _roots = new();
private UITree _tree = new();
private ModuleViewer _mv = new();
private ConfigRoot _root;
private WorldState _ws;

private readonly Lumina.Excel.ExcelSheet<ExVersion> _exSheet;

public ConfigUI(ConfigRoot config, WorldState ws)
{
_root = config;
_ws = ws;

_exSheet = Service.LuminaGameData!.GetExcelSheet<ExVersion>()!;

Dictionary<Type, UINode> nodes = new();
foreach (var n in config.Nodes)
{
Expand Down Expand Up @@ -75,60 +66,10 @@ public void Draw()
DrawNodes(_roots);
using (var tab = ImRaii.TabItem("Modules"))
if (tab)
DrawModules();
_mv.Draw(_tree);
}
}

private void DrawModules()
{
// TODO: separate unreals from trials and alliance raids from raids and show old unreals in uncatalogued
// support for fates and CEs to have nameIDs
foreach (var expac in ModuleRegistry.AvailableExpansions)
{
var expac_mods = ModuleRegistry.CataloguedModules.Where(x => x.ExVersion == expac);
var expac_cont = expac_mods.Select(x => x.ContentType).Distinct();
UIMisc.TextUnderlined(ImGuiColors.DalamudViolet, $"{_exSheet.GetRow(expac)!.Name}");
foreach (var cont in expac_cont)
{
ImGui.Indent();
UIMisc.TextUnderlined(ImGuiColors.TankBlue, $"{(cont!.RawString.IsNullOrEmpty() ? "Unknown" : cont)}");
ModuleRegistry.Info? prevMod = null;
foreach (var mod in expac_mods.Where(x => x.ContentType == cont))
{
if (prevMod == null || prevMod.InstanceName != mod.InstanceName || mod.IsCriticalEngagement())
{
ImGui.Indent();
var displayName = mod.IsHunt() ? $"[{mod.HuntRank}] {mod.BossName}" ?? ""
: mod.IsCriticalEngagement() ? $"[CE] {mod.ForayName}" ?? ""
: mod.IsCarnivale() ? $"[{mod.CarnivaleStage}] {mod.BossName}" ?? ""
: mod.InstanceName ?? "";
foreach (var x in _tree.Node($"{CultureInfo.InvariantCulture.TextInfo.ToTitleCase(displayName)}###{mod.PrimaryActorOID}"))
{
DrawBosses(expac_mods, mod.CFCID);
}
ImGui.Unindent();
}
prevMod = mod;
}
ImGui.Unindent();
}
}

if (ModuleRegistry.UncataloguedModules.Any())
{
UIMisc.TextUnderlined(ImGuiColors.DPSRed, $"Uncatalogued");
foreach (var mod in ModuleRegistry.UncataloguedModules)
ImGui.Text($"{mod.ModuleType.Name}");
}
}

private static void DrawBosses(IEnumerable<ModuleRegistry.Info> expac_mods, uint cfcID)
{
foreach (var mod in expac_mods.Where(x => x.CFCID == cfcID && cfcID != 0))
if (!mod.BossName!.RawString.IsNullOrEmpty())
ImGui.Text($"{CultureInfo.InvariantCulture.TextInfo.ToTitleCase(mod.BossName)}");
}

private static string GenerateNodeName(Type t) => t.Name.EndsWith("Config") ? t.Name.Remove(t.Name.Length - "Config".Length) : t.Name;

private void SortByOrder(List<UINode> nodes)
Expand Down
131 changes: 131 additions & 0 deletions BossMod/Config/ModuleViewer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Lumina.Text;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Numerics;

namespace BossMod;

internal class ModuleViewer
{
private const int SpaceBetweenFilterWidgets = 3;
private const int IconSize = 30;
private readonly Dictionary<uint, bool> ExpansionFilter;
private readonly Dictionary<SeString, bool> ContentFilter;
private static readonly Lumina.Excel.ExcelSheet<ExVersion> _exSheet = Service.LuminaGameData!.GetExcelSheet<ExVersion>()!;
protected static ImGuiTableFlags TABLE_FLAG = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.BordersV | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.ScrollX | ImGuiTableFlags.NoHostExtendX;

private Dictionary<uint, int> exversionToIconID = new()
{
{0, 61875},
{1, 61876},
{2, 61877},
{3, 61878},
{4, 61879},
};

public ModuleViewer()
{
ExpansionFilter = ModuleRegistry.AvailableExpansions.ToDictionary(e => e, e => true);
ContentFilter = ModuleRegistry.AvailableContent.ToDictionary(c => c, c => true);
}

public static string GetExpansionName(uint id) => _exSheet.GetRow(id)!.Name;

public static IDalamudTextureWrap? GetIcon(int iconId) => Service.Texture.GetIcon((uint)iconId, Dalamud.Plugin.Services.ITextureProvider.IconFlags.HiRes);
public static IDalamudTextureWrap? GetIcon(uint iconId) => GetIcon((int)iconId);

public void Draw(UITree _tree)
{
using (var group = ImRaii.Group())
{
using var table = ImRaii.Table("Filters", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.NoHostExtendX | ImGuiTableFlags.SizingFixedSame | ImGuiTableFlags.ScrollY);
if (!table) return;
ImGui.TableNextColumn();
ImGui.TableHeader("Expansion");

ImGui.TableNextRow(ImGuiTableRowFlags.None);
ImGui.TableNextColumn();
DrawExpansionFilters();
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TableHeader("Content");

ImGui.TableNextRow(ImGuiTableRowFlags.None);
ImGui.TableNextColumn();
DrawContentTypeFilters();
}
ImGui.SameLine();
using (var group = ImRaii.Group())
DrawModules(_tree);
}

public void DrawExpansionFilters()
{
foreach (var expac in ModuleRegistry.AvailableExpansions)
{
var icon = GetIcon(exversionToIconID[expac]);
if (icon != null)
{
UIMisc.ImageToggleButton(icon, new Vector2(IconSize, IconSize), ExpansionFilter[expac]);
if (ImGui.IsItemClicked()) ExpansionFilter[expac] = !ExpansionFilter[expac];
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) ExpansionFilter.Keys.Except([expac]).ToList().ForEach(k => ExpansionFilter[k] = !ExpansionFilter[k]);
ImGui.SameLine();
}
UIMisc.TextV($"{_exSheet.GetRow(expac)!.Name}");
}
}

public void DrawContentTypeFilters()
{
foreach (var cont in ModuleRegistry.AvailableContentIcons)
{
var icon = GetIcon(cont.Value);
if (icon != null)
{
UIMisc.ImageToggleButton(icon, new Vector2(IconSize, IconSize), ContentFilter[cont.Key]);
if (ImGui.IsItemClicked()) ContentFilter[cont.Key] = !ContentFilter[cont.Key];
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) ContentFilter.Keys.Except([cont.Key]).ToList().ForEach(k => ContentFilter[k] = !ContentFilter[k]);
ImGui.SameLine();
}
UIMisc.TextV($"{cont.Key}");
}
}

public void DrawModules(UITree _tree)
{
using var table = ImRaii.Table("ModulesTable", 2, TABLE_FLAG);
if (!table) return;

DrawRows(ModuleRegistry.CataloguedModules.DistinctBy(x => x.DisplayName), _tree);
DrawRows(ModuleRegistry.UncataloguedModules, _tree);
}

private void DrawRows(IEnumerable<ModuleRegistry.Info> enumerable, UITree _tree)
{
foreach (var mod in enumerable)
{
if (!ContentFilter[mod.ContentType ?? new()] || !ExpansionFilter[mod.ExVersion]) continue;

ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Image(Service.Texture.GetIcon(mod.ContentIcon)!.ImGuiHandle, new Vector2(36));
ImGui.TableNextColumn();
foreach (var _ in _tree.Node($"{CultureInfo.InvariantCulture.TextInfo.ToTitleCase(mod.DisplayName!)}##{mod.PrimaryActorOID}"))
DrawBosses(ModuleRegistry.RegisteredModules.Values, mod.DisplayName ?? new());
}
}

private void DrawBosses(IEnumerable<ModuleRegistry.Info> modules, SeString name)
{
foreach (var mod in modules.Where(x => x.DisplayName == name))
if (!mod.BossName!.RawString.IsNullOrEmpty())
ImGui.TextUnformatted($"{CultureInfo.InvariantCulture.TextInfo.ToTitleCase(mod.BossName)}");
}
}
55 changes: 55 additions & 0 deletions BossMod/Modules/Endwalker/SoloDuty/Endwalker/AetherialRay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;

namespace BossMod.Modules.Endwalker.SoloDuty.Endwalker;
class AetherialRay : Components.GenericBaitAway
{
private DateTime _activation;
private bool active;

public AetherialRay() : base(centerAtTarget: true) { }

public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.AetherialRay)
{
active = true;
_activation = spell.NPCFinishAt;
}
}

public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.AetherialRay)
++NumCasts;
}

public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID == AID.AetherialRayVisual)
{
++NumCasts;
if (NumCasts == 5)
{
CurrentBaits.Clear();
NumCasts = 0;
active = false;
}
}
}

public override void AddGlobalHints(BossModule module, GlobalHints hints)
{
if (active)
hints.Add("Tankbuster 5x");
}

public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (active)
{
BitMask targets = new();
targets.Set(module.Raid.FindSlot(module.Raid.Player()!.TargetID));
hints.PredictedDamage.Add((targets, _activation));
}
}
}
70 changes: 70 additions & 0 deletions BossMod/Modules/Endwalker/SoloDuty/Endwalker/AkhMorn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Linq;

namespace BossMod.Modules.Endwalker.SoloDuty.Endwalker;
class AkhMorn : Components.GenericBaitAway
{
private DateTime _activation;
private bool active;

public AkhMorn() : base(centerAtTarget: true) { }

public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.AkhMorn)
{
CurrentBaits.Add(new(module.PrimaryActor, module.Raid.Player()!, new AOEShapeCircle(4)));
active = true;
_activation = spell.NPCFinishAt;
}
}

public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.AkhMorn)
++NumCasts;
}

public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell)
{
if ((AID)spell.Action.ID == AID.AkhMornVisual)
{
++NumCasts;
if (NumCasts == 6 && module.Enemies(OID.ZenosP1).Any(x => !x.IsDead))
{
CurrentBaits.Clear();
NumCasts = 0;
active = false;
}
if (NumCasts == 8 && module.Enemies(OID.ZenosP1).Any(x => x.IsDead))
{
CurrentBaits.Clear();
NumCasts = 0;
active = false;
}
}
}

public override void AddGlobalHints(BossModule module, GlobalHints hints)
{
if (active && module.Enemies(OID.ZenosP1).Any(x => !x.IsDead))
hints.Add("Tankbuster 6x");
if (active && module.Enemies(OID.ZenosP1).Any(x => x.IsDead))
hints.Add("Tankbuster 8x");
}

public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
if (active)
{
BitMask targets = new();
targets.Set(module.Raid.FindSlot(module.Raid.Player()!.TargetID));
if (module.Enemies(OID.ZenosP1).Any(x => !x.IsDead))
for (int i = 1; i < 7; ++i)
hints.PredictedDamage.Add((targets, _activation.AddSeconds(i * 0.7f)));
if (module.Enemies(OID.ZenosP1).Any(x => x.IsDead))
for (int i = 1; i < 9; ++i)
hints.PredictedDamage.Add((targets, _activation.AddSeconds(i * 0.7f)));
}
}
}
27 changes: 27 additions & 0 deletions BossMod/Modules/Endwalker/SoloDuty/Endwalker/Candlewick.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace BossMod.Modules.Endwalker.SoloDuty.Endwalker;
class Candlewick : Components.ConcentricAOEs
{
private static AOEShape[] _shapes = { new AOEShapeCircle(10), new AOEShapeDonut(10, 30) };

public Candlewick() : base(_shapes) { }

public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell)
{
if ((AID)spell.Action.ID == AID.CandlewickPointBlank)
AddSequence(caster.Position, spell.NPCFinishAt.AddSeconds(2.03f));
}

public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell)
{
if (Sequences.Count > 0)
{
var order = (AID)spell.Action.ID switch
{
AID.CandlewickPointBlank => 0,
AID.CandlewickDonut => 1,
_ => -1
};
AdvanceSequence(order, caster.Position, module.WorldState.CurrentTime.AddSeconds(2.05f));
}
}
}
Loading