diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 6350da4aa..b2a11d774 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -290,6 +290,8 @@ namespace Dalamud.Configuration.Internal /// public bool ShowTsm { get; set; } = true; + public bool Fools22 { get; set; } = true; + /// /// Load a configuration from the provided path. /// diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 537ffa516..4ad19e5fa 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -53,6 +53,8 @@ namespace Dalamud #endregion + private Fools22? fools22; + /// /// Initializes a new instance of the class. /// @@ -270,6 +272,15 @@ namespace Dalamud Troubleshooting.LogTroubleshooting(); + try + { + this.fools22 = new Fools22(); + } + catch (Exception ex) + { + Log.Error(ex, "Fools22 load failed."); + } + Log.Information("Dalamud is ready."); } catch (Exception ex) @@ -360,6 +371,8 @@ namespace Dalamud this.processMonoHook?.Dispose(); + this.fools22?.Dispose(); + Log.Debug("Dalamud::Dispose() OK!"); } catch (Exception ex) diff --git a/Dalamud/Fools22.cs b/Dalamud/Fools22.cs new file mode 100644 index 000000000..c384f3641 --- /dev/null +++ b/Dalamud/Fools22.cs @@ -0,0 +1,315 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Media; +using System.Numerics; + +using Dalamud.Configuration.Internal; +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.Gui; +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface; +using Dalamud.Interface.Animation; +using Dalamud.Interface.Animation.EasingFunctions; +using Dalamud.Interface.Internal; +using Dalamud.Utility; +using ImGuiNET; +using ImGuiScene; +using Lumina.Excel.GeneratedSheets; +using Serilog; +using Condition = Dalamud.Game.ClientState.Conditions.Condition; + +namespace Dalamud; + +public class Fools22 : IDisposable +{ + private readonly TextureWrap erDeathBgTexture; + private readonly TextureWrap erNormalDeathTexture; + private readonly TextureWrap erCraftFailedTexture; + + private readonly string synthesisFailsMessage; + + private readonly Stopwatch time = new Stopwatch(); + private readonly SoundPlayer player; + + private bool assetsReady = false; + + private AnimationState currentState = AnimationState.NotPlaying; + private DeathType currentDeathType = DeathType.Death; + + private Easing alphaEasing; + private Easing scaleEasing; + + private bool lastFrameUnconscious = false; + + private int msFadeInTime = 1000; + private int msFadeOutTime = 2000; + private int msWaitTime = 1600; + + private TextureWrap TextTexture => this.currentDeathType switch + { + DeathType.Death => this.erNormalDeathTexture, + DeathType.CraftFailed => this.erCraftFailedTexture, + }; + + private enum AnimationState + { + NotPlaying, + FadeIn, + Wait, + FadeOut, + } + + private enum DeathType + { + Death, + CraftFailed, + } + + public Fools22() + { + var dalamud = Service.Get(); + var interfaceManager = Service.Get(); + var framework = Service.Get(); + var chatGui = Service.Get(); + var dataMgr = Service.Get(); + + this.erDeathBgTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "fools22", "er_death_bg.png"))!; + this.erNormalDeathTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "fools22", "er_normal_death.png"))!; + this.erCraftFailedTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "fools22", "er_craft_failed.png"))!; + + var soundBytes = File.ReadAllBytes(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "fools22", "snd_death_er.wav")); + this.player = new SoundPlayer(new MemoryStream(soundBytes)); + + if (this.erDeathBgTexture == null || this.erNormalDeathTexture == null || this.erCraftFailedTexture == null) + { + Log.Error("Fools22: Failed to load images"); + return; + } + + this.synthesisFailsMessage = dataMgr.GetExcelSheet()!.GetRow(1160)!.Text.ToDalamudString().TextValue; + + this.assetsReady = true; + + interfaceManager.Draw += this.Draw; + framework.Update += this.FrameworkOnUpdate; + chatGui.ChatMessage += this.ChatGuiOnChatMessage; + } + + private void ChatGuiOnChatMessage(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool ishandled) + { + if (message.TextValue.Contains(this.synthesisFailsMessage)) + { + this.PlayAnimation(DeathType.CraftFailed); + Log.Information("Fools22: Craft failed"); + } + } + + private void FrameworkOnUpdate(Framework framework) + { + var condition = Service.Get(); + var isUnconscious = condition[ConditionFlag.Unconscious]; + + if (isUnconscious && !this.lastFrameUnconscious) + { + this.PlayAnimation(DeathType.Death); + Log.Information("Fools22: Player died"); + } + + this.lastFrameUnconscious = isUnconscious; + } + + private void Draw() + { +#if DEBUG + if (ImGui.Begin("fools test")) + { + if (ImGui.Button("play death")) + { + this.PlayAnimation(DeathType.Death); + } + + if (ImGui.Button("play craft failed")) + { + this.PlayAnimation(DeathType.CraftFailed); + } + + ImGui.InputInt("fade in time", ref this.msFadeInTime); + ImGui.InputInt("fade out time", ref this.msFadeOutTime); + ImGui.InputInt("wait time", ref this.msWaitTime); + + ImGui.TextUnformatted("state: " + this.currentState); + ImGui.TextUnformatted("time: " + this.time.ElapsedMilliseconds); + + ImGui.TextUnformatted("scale: " + this.scaleEasing?.EasedPoint.X); + } + + ImGui.End(); +#endif + var vpSize = ImGuiHelpers.MainViewport.Size; + + ImGui.SetNextWindowPos(new Vector2(0, 0), ImGuiCond.Always); + ImGui.SetNextWindowSize(new Vector2(vpSize.X, vpSize.Y), ImGuiCond.Always); + ImGuiHelpers.ForceNextWindowMainViewport(); + + ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0); + ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0); + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); + ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 0)); + ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0, 0, 0, 0)); + ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 0)); + + if (ImGui.Begin("fools22", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus + | ImGuiWindowFlags.NoMouseInputs | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoScrollbar)) + { + if (this.currentState != AnimationState.NotPlaying) + { + this.alphaEasing?.Update(); + this.scaleEasing?.Update(); + } + + switch (this.currentState) + { + case AnimationState.FadeIn: + this.FadeIn(vpSize); + break; + case AnimationState.Wait: + this.Wait(vpSize); + break; + case AnimationState.FadeOut: + this.FadeOut(vpSize); + break; + } + } + + ImGui.End(); + + ImGui.PopStyleColor(3); + ImGui.PopStyleVar(3); + } + + private static void AdjustCursorAndDraw(Vector2 vpSize, TextureWrap tex, float scale = 1.0f) + { + var width = vpSize.X; + var height = tex.Height / (float)tex.Width * width; + + if (height < vpSize.Y) + { + height = vpSize.Y; + width = tex.Width / (float)tex.Height * height; + ImGui.SetCursorPosX((vpSize.X - width) / 2); + } + else + { + ImGui.SetCursorPosY((vpSize.Y - height) / 2); + } + + var scaledSize = new Vector2(width, height) * scale; + var difference = scaledSize - vpSize; + + var cursor = ImGui.GetCursorPos(); + ImGui.SetCursorPos(cursor - (difference / 2)); + + ImGui.Image(tex.ImGuiHandle, scaledSize); + } + + private void FadeIn(Vector2 vpSize) + { + if (this.time.ElapsedMilliseconds > this.msFadeInTime) + this.currentState = AnimationState.Wait; + + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)this.alphaEasing.Value); + + AdjustCursorAndDraw(vpSize, this.erDeathBgTexture); + AdjustCursorAndDraw(vpSize, this.TextTexture, this.scaleEasing.EasedPoint.X); + + ImGui.PopStyleVar(); + } + + private void Wait(Vector2 vpSize) + { + if (this.time.ElapsedMilliseconds > this.msFadeInTime + this.msWaitTime) + { + this.currentState = AnimationState.FadeOut; + this.alphaEasing = new InOutCubic(TimeSpan.FromMilliseconds(this.msFadeOutTime)); + this.alphaEasing.Start(); + } + + AdjustCursorAndDraw(vpSize, this.erDeathBgTexture); + AdjustCursorAndDraw(vpSize, this.TextTexture, this.scaleEasing.EasedPoint.X); + } + + private void FadeOut(Vector2 vpSize) + { + if (this.time.ElapsedMilliseconds > this.msFadeInTime + this.msWaitTime + this.msFadeOutTime) + { + this.currentState = AnimationState.NotPlaying; + this.time.Stop(); + } + + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1 - (float)this.alphaEasing.Value); + + AdjustCursorAndDraw(vpSize, this.erDeathBgTexture); + AdjustCursorAndDraw(vpSize, this.TextTexture, this.scaleEasing.EasedPoint.X); + + ImGui.PopStyleVar(); + } + + private void PlayAnimation(DeathType type) + { + if (!this.CheckFoolsApplicable()) + return; + + if (this.currentState != AnimationState.NotPlaying) + return; + + this.currentDeathType = type; + + this.currentState = AnimationState.FadeIn; + this.alphaEasing = new InOutCubic(TimeSpan.FromMilliseconds(this.msFadeInTime)); + this.alphaEasing.Start(); + + this.scaleEasing = new OutCubic(TimeSpan.FromMilliseconds(this.msFadeInTime + this.msWaitTime + this.msFadeOutTime)) + { + Point1 = new Vector2(0.95f, 0.95f), + Point2 = new Vector2(1.05f, 1.05f), + }; + this.scaleEasing.Start(); + + this.time.Reset(); + this.time.Start(); + + this.player.Play(); + } + + private bool CheckFoolsApplicable() + { + var config = Service.Get(); + + if (!config.Fools22) + return false; + + if (!(DateTime.Now.Month == 4 && DateTime.Now.Day == 1)) + return false; + + var timeZone = TimeZoneInfo.Local; + var offset = timeZone.GetUtcOffset(DateTime.UtcNow); + + Log.Information("Fools22: UTC offset: {0}", offset); + + return this.assetsReady; + } + + public void Dispose() + { + this.erDeathBgTexture.Dispose(); + this.erNormalDeathTexture.Dispose(); + this.erCraftFailedTexture.Dispose(); + + this.player.Dispose(); + } + +} diff --git a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs index 686839d0f..9ff94f42e 100644 --- a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs @@ -49,6 +49,8 @@ namespace Dalamud.Interface.Internal.Windows private bool doFocus; private bool doTsm; + private bool doFools22; + private List? dtrOrder; private List? dtrIgnore; private int dtrSpacing; @@ -104,6 +106,8 @@ namespace Dalamud.Interface.Internal.Windows this.doFocus = configuration.IsFocusManagementEnabled; this.doTsm = configuration.ShowTsm; + this.doFools22 = configuration.Fools22; + this.dtrSpacing = configuration.DtrSpacing; this.dtrSwapDirection = configuration.DtrSwapDirection; @@ -236,6 +240,12 @@ namespace Dalamud.Interface.Internal.Windows private void DrawGeneralTab() { + if (DateTime.Now.Month == 4 && DateTime.Now.Day == 1) + { + ImGui.Checkbox(Loc.Localize("Fools22", "Enable April Fools 2022"), ref this.doFools22); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("Fools22Hint", "Enables the April Fools 2022 funny joke. Turn this off if you don't think it's funny.")); + } + ImGui.Text(Loc.Localize("DalamudSettingsLanguage", "Language")); ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length); ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in.")); @@ -843,6 +853,8 @@ namespace Dalamud.Interface.Internal.Windows configuration.IsFocusManagementEnabled = this.doFocus; configuration.ShowTsm = this.doTsm; + configuration.Fools22 = this.doFools22; + configuration.UseAxisFontsFromGame = this.doUseAxisFontsFromGame; configuration.FontGamma = this.fontGamma;