From a146685f7cbabb0cb23adba714d392956aee68ad Mon Sep 17 00:00:00 2001 From: Xorus Date: Sun, 19 Nov 2023 20:40:39 +0100 Subject: [PATCH] alarm base code - wip for #53 --- Plugin/Configuration/ConfigurationFile.cs | 1 + .../Legacy/CombatAlarmsConfiguration.cs | 54 ++++++ Plugin/Game/SFXPlay.cs | 20 ++- Plugin/Game/TickingSound.cs | 18 +- Plugin/Plugin.cs | 9 +- Plugin/Status/CombatAlarm.cs | 165 ++++++++++++++++++ Plugin/Status/State.cs | 4 + Plugin/Ui/FloatingWindow.cs | 7 +- Plugin/Ui/PluginUi.cs | 1 + Plugin/Ui/SettingsTab/AlarmsTab.cs | 64 +++++++ Plugin/Ui/SfxDebug.cs | 41 +++++ 11 files changed, 361 insertions(+), 23 deletions(-) create mode 100644 Plugin/Configuration/Legacy/CombatAlarmsConfiguration.cs create mode 100644 Plugin/Status/CombatAlarm.cs create mode 100644 Plugin/Ui/SettingsTab/AlarmsTab.cs create mode 100644 Plugin/Ui/SfxDebug.cs diff --git a/Plugin/Configuration/ConfigurationFile.cs b/Plugin/Configuration/ConfigurationFile.cs index 7373ec7..be10779 100644 --- a/Plugin/Configuration/ConfigurationFile.cs +++ b/Plugin/Configuration/ConfigurationFile.cs @@ -38,6 +38,7 @@ public enum TextAlign public DtrConfiguration Dtr = new(); public FloatingWindowConfiguration FloatingWindow = new(); public WebServerConfiguration WebServer = new(); + public CombatAlarmsConfiguration CombatAlarms = new(); public int Version { get; set; } = 3; diff --git a/Plugin/Configuration/Legacy/CombatAlarmsConfiguration.cs b/Plugin/Configuration/Legacy/CombatAlarmsConfiguration.cs new file mode 100644 index 0000000..7383f70 --- /dev/null +++ b/Plugin/Configuration/Legacy/CombatAlarmsConfiguration.cs @@ -0,0 +1,54 @@ +// This file is part of EngageTimer +// Copyright (C) 2023 Xorus +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.Collections.Generic; +using System.Numerics; + +namespace EngageTimer.Configuration.Legacy; + +public class CombatAlarmsConfiguration +{ + public enum TextType + { + DalamudNotification = 0, + GameToast = 1, + ChatLogMessage = 2 + } + + public class Alarm + { + public bool Enabled = true; + public int StartTime; + public int Duration; + public string? Text; + public Vector4? Color; + public int? Sfx; + public TextType TextType = TextType.DalamudNotification; + public bool Blink; + } + + public readonly List Alarms = new() + { + // new Alarm() + // { + // StartTime = 5, + // Duration = 30, + // TextType = TextType.DalamudNotification, + // Color = new Vector4(255, 0, 0, 1), + // Text = "Potion Window!", + // Sfx = 16 + // } + }; +} \ No newline at end of file diff --git a/Plugin/Game/SFXPlay.cs b/Plugin/Game/SFXPlay.cs index d3565b4..351a97c 100644 --- a/Plugin/Game/SFXPlay.cs +++ b/Plugin/Game/SFXPlay.cs @@ -37,19 +37,27 @@ public GameSound() public class SfxPlay { + public const uint FirstSeSfx = 37 - 1; public const uint SmallTick = 29; public const uint CdTick = 48; private readonly GameSound _gameSound = new(); - public void SoundEffect(uint id) + public SfxPlay() + { + /* Force a sound to play on load as a workaround for the CLR taking some time to init the pointy method call, + * we dont want a freeze midway through a countdown + * https://discord.com/channels/581875019861328007/653504487352303619/988123102116450335 + * https://i.imgur.com/BrLUr2p.png + * */ + SoundEffect(0); // should be cursor sound + } + + public unsafe void SoundEffect(uint id) { // var s = new Stopwatch(); // s.Start(); - unsafe - { - _gameSound.PlaySoundEffect(id, IntPtr.Zero, IntPtr.Zero, 0); - } + _gameSound.PlaySoundEffect(id, IntPtr.Zero, IntPtr.Zero, 0); // s.Stop(); - // PluginIoC.Logger.Debug("Sound play took " + s.ElapsedMilliseconds + "ms"); + // Plugin.Logger.Debug("Sound play took " + s.ElapsedMilliseconds + "ms"); } } \ No newline at end of file diff --git a/Plugin/Game/TickingSound.cs b/Plugin/Game/TickingSound.cs index 545a1bd..c3ecc2f 100644 --- a/Plugin/Game/TickingSound.cs +++ b/Plugin/Game/TickingSound.cs @@ -19,28 +19,16 @@ namespace EngageTimer.Game; public class TickingSound { - private readonly SfxPlay _sfx = new(); - private int? _lastNumberPlayed; - // This is a workaround for CLR taking some time to init the pointy method call. - private bool _soundLoaded; - public void Update() { // if (!_configuration.DisplayCountdown) return; var configuration = Plugin.Config; var state = Plugin.State; if (!configuration.Countdown.EnableTickingSound || state.Mocked) return; - if (!_soundLoaded) - { - _sfx.SoundEffect(0); // should be cursor sound - _soundLoaded = true; - return; - } - - if (state.CountingDown && - state.CountDownValue > 5 && state.CountDownValue <= configuration.Countdown.StartTickingFrom) + if (state is { CountingDown: true, CountDownValue: > 5 } && + state.CountDownValue <= configuration.Countdown.StartTickingFrom) TickSound((int)Math.Ceiling(state.CountDownValue)); } @@ -50,6 +38,6 @@ private void TickSound(int n) if (!configuration.Countdown.EnableTickingSound || _lastNumberPlayed == n) return; _lastNumberPlayed = n; - _sfx.SoundEffect(configuration.Countdown.UseAlternativeSound ? SfxPlay.SmallTick : SfxPlay.CdTick); + Plugin.SfxPlay.SoundEffect(configuration.Countdown.UseAlternativeSound ? SfxPlay.SmallTick : SfxPlay.CdTick); } } \ No newline at end of file diff --git a/Plugin/Plugin.cs b/Plugin/Plugin.cs index 54b3ea9..1d2f2cc 100644 --- a/Plugin/Plugin.cs +++ b/Plugin/Plugin.cs @@ -19,6 +19,7 @@ using Dalamud.Plugin.Services; using EngageTimer.Commands; using EngageTimer.Configuration; +using EngageTimer.Game; using EngageTimer.Status; using EngageTimer.Ui; using JetBrains.Annotations; @@ -39,6 +40,7 @@ public sealed class Plugin : IDalamudPlugin [PluginService] public static IGameInteropProvider GameInterop { get; private set; } = null!; [PluginService] public static IPluginLog Logger { get; private set; } = null!; [PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!; + [PluginService] public static IToastGui ToastGui { get; private set; } = null!; public static ConfigurationFile Config { get; private set; } = null!; public static State State { get; private set; } = null!; public static Translator Translator { get; private set; } = null!; @@ -48,6 +50,8 @@ public sealed class Plugin : IDalamudPlugin private static FrameworkThings FrameworkThings { get; set; } = null!; private static MainCommand MainCommand { get; set; } = null!; private static SettingsCommand SettingsCommand { get; set; } = null!; + private static CombatAlarm CombatAlarm { get; set; } = null!; + public static SfxPlay SfxPlay { get; set; } = null!; public Plugin(DalamudPluginInterface pluginInterface) { @@ -60,15 +64,18 @@ public Plugin(DalamudPluginInterface pluginInterface) MainCommand = new MainCommand(); SettingsCommand = new SettingsCommand(); PluginUi = new PluginUi(); + CombatAlarm = new CombatAlarm(); + SfxPlay = new SfxPlay(); } void IDisposable.Dispose() { PluginInterface.SavePluginConfig(Config); - Translator.Dispose(); + CombatAlarm.Dispose(); PluginUi.Dispose(); FrameworkThings.Dispose(); MainCommand.Dispose(); SettingsCommand.Dispose(); + Translator.Dispose(); } } \ No newline at end of file diff --git a/Plugin/Status/CombatAlarm.cs b/Plugin/Status/CombatAlarm.cs new file mode 100644 index 0000000..8ab013a --- /dev/null +++ b/Plugin/Status/CombatAlarm.cs @@ -0,0 +1,165 @@ +// This file is part of EngageTimer +// Copyright (C) 2023 Xorus +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Net.Mime; +using Dalamud.Game.Gui.Toast; +using Dalamud.Game.Text; +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Plugin.Services; +using EngageTimer.Configuration.Legacy; +using EngageTimer.Game; + +namespace EngageTimer.Status; + +public sealed class CombatAlarm : IDisposable +{ + private enum AlarmActionType + { + Start, + Stop + } + + private class AlarmAction + { + public AlarmActionType Type { get; init; } + public int Id { get; init; } + public CombatAlarmsConfiguration.Alarm Config { get; init; } = null!; + } + + private readonly Dictionary _alarms = new(); + + // private readonly List _rangAlarms = new(); + private int? _lastCheck = null; + + public CombatAlarm() + { + Plugin.Config.OnSave += ConfigurationChanged; + Plugin.Framework.Update += FrameworkUpdate; + ConfigurationChanged(null, EventArgs.Empty); + Plugin.State.InCombatChanged += InCombatChanged; + } + + + private void ConfigurationChanged(object? sender, EventArgs e) + { + _alarms.Clear(); + for (var index = 0; index < Plugin.Config.CombatAlarms.Alarms.Count; index++) + { + var alarm = Plugin.Config.CombatAlarms.Alarms[index]; + _alarms[alarm.StartTime] = new AlarmAction() + { + Type = AlarmActionType.Start, + Id = index, + Config = alarm + }; + _alarms[alarm.StartTime + alarm.Duration] = new AlarmAction() + { + Type = AlarmActionType.Stop, + Id = index, + Config = alarm + }; + } + } + + private void InCombatChanged(object? sender, EventArgs e) + { + if (!Plugin.State.InCombat) + { + // + clear whatever alarms are up + // _rangAlarms.Clear(); + ClearAlarms(); + } + } + + private void FrameworkUpdate(IFramework framework) + { + if (!Plugin.State.InCombat) return; + + // only run once a second + var time = (int)Math.Floor(Plugin.State.CombatDuration.TotalSeconds); + if (_lastCheck == time) return; + _lastCheck = time; + + if (!_alarms.TryGetValue(time, out var alarm)) return; + + if (alarm.Type == AlarmActionType.Start) + { + RunAlarm(alarm.Config); + // _rangAlarms.Add(time); + } + + if (alarm.Type == AlarmActionType.Stop) + { + ClearAlarms(); + } + } + + private void RunAlarm(CombatAlarmsConfiguration.Alarm alarm) + { + if (alarm.Sfx != null) + { + // Plugin.SfxPlay.SoundEffect(); + Plugin.SfxPlay.SoundEffect((uint)(SfxPlay.FirstSeSfx + alarm.Sfx)); + } + + var trimText = alarm.Text?.Trim(); + if (trimText is { Length: > 0 }) + { + switch (alarm.TextType) + { + case CombatAlarmsConfiguration.TextType.DalamudNotification: + Plugin.PluginInterface.UiBuilder.AddNotification( + trimText, + "EngageTimer", + NotificationType.Info, + 8000 + ); + break; + case CombatAlarmsConfiguration.TextType.GameToast: + Plugin.ToastGui.ShowNormal(trimText); + break; + case CombatAlarmsConfiguration.TextType.ChatLogMessage: + // Plugin.ChatGui.Print(trimText, "EngageTimer"); + Plugin.ChatGui.Print(new XivChatEntry() + { + Type = XivChatType.Echo, + Name = "EngageTimer", + Message = trimText + }); + break; + } + } + + if (alarm.Color != null) + { + Plugin.State.OverrideFwColor = alarm.Color; + Plugin.State.BlinkStopwatch = alarm.Blink; + } + } + + private void ClearAlarms() + { + Plugin.State.OverrideFwColor = null; + Plugin.State.BlinkStopwatch = false; + } + + public void Dispose() + { + Plugin.Config.OnSave -= ConfigurationChanged; + Plugin.Framework.Update -= FrameworkUpdate; + } +} \ No newline at end of file diff --git a/Plugin/Status/State.cs b/Plugin/Status/State.cs index 65abc5b..dc080a5 100644 --- a/Plugin/Status/State.cs +++ b/Plugin/Status/State.cs @@ -14,6 +14,7 @@ // along with this program. If not, see . using System; +using System.Numerics; namespace EngageTimer.Status; @@ -26,6 +27,9 @@ public class State public DateTime CombatStart { get; set; } public bool Mocked { get; set; } + public Vector4? OverrideFwColor { get; set; } + public bool BlinkStopwatch { get; set; } = false; + public bool InCombat { get => _inCombat; diff --git a/Plugin/Ui/FloatingWindow.cs b/Plugin/Ui/FloatingWindow.cs index d46d7ff..3b82c11 100644 --- a/Plugin/Ui/FloatingWindow.cs +++ b/Plugin/Ui/FloatingWindow.cs @@ -106,7 +106,12 @@ private void DrawWindow(bool stopwatchActive, bool countdownActive) if (ImGui.Begin("EngageTimer stopwatch", ref _stopwatchVisible, flags)) { - ImGui.PushStyleColor(ImGuiCol.Text, Plugin.Config.FloatingWindow.TextColor); + var color = Plugin.State.OverrideFwColor ?? Plugin.Config.FloatingWindow.TextColor; + // use time to change color every half second + if (Plugin.State.BlinkStopwatch && ImGui.GetTime() % 1 < 0.5) + color = Plugin.Config.FloatingWindow.TextColor; + + ImGui.PushStyleColor(ImGuiCol.Text, color); ImGui.SetWindowFontScale(Plugin.Config.FloatingWindow.Scale); var stopwatchDecimals = Plugin.Config.FloatingWindow.DecimalStopwatchPrecision > 0; diff --git a/Plugin/Ui/PluginUi.cs b/Plugin/Ui/PluginUi.cs index b230b55..619531f 100644 --- a/Plugin/Ui/PluginUi.cs +++ b/Plugin/Ui/PluginUi.cs @@ -30,6 +30,7 @@ public PluginUi() Plugin.NumberTextures = new NumberTextures(); _windowSystem = new WindowSystem("Engage Timer"); _windowSystem.AddWindow(_settings); + // _windowSystem.AddWindow(new SfxDebug()); Plugin.PluginInterface.UiBuilder.Draw += Draw; Plugin.PluginInterface.UiBuilder.OpenConfigUi += OpenSettings; } diff --git a/Plugin/Ui/SettingsTab/AlarmsTab.cs b/Plugin/Ui/SettingsTab/AlarmsTab.cs new file mode 100644 index 0000000..aec6aec --- /dev/null +++ b/Plugin/Ui/SettingsTab/AlarmsTab.cs @@ -0,0 +1,64 @@ +// This file is part of EngageTimer +// Copyright (C) 2023 Xorus +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using Dalamud.Interface; +using EngageTimer.Configuration.Legacy; +using ImGuiNET; + +namespace EngageTimer.Ui.SettingsTab; + +public static class AlarmsTab +{ + public static void Draw() + { + ImGui.Text("Some explanation text here"); + ImGui.Separator(); + + ImGui.Text("Alarm list"); + if (Plugin.Config.CombatAlarms.Alarms.Count == 0) + { + ImGui.Text("No alarms set"); + return; + } + + for (var index = 0; index < Plugin.Config.CombatAlarms.Alarms.Count; index++) + { + AlarmElement(index, Plugin.Config.CombatAlarms.Alarms[index]); + } + + if (ImGui.Button("add")) + { + } + } + + private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alarm) + { + ImGui.PushID("alarm_" + index); + ImGui.BeginGroup(); + Components.IconButton(FontAwesomeIcon.Trash, "delete_" + index, + () => Plugin.Config.CombatAlarms.Alarms.RemoveAt(index)); + ImGui.SameLine(); + ImGui.Text("Alarm " + index); + ImGui.EndGroup(); + + ImGui.Indent(); + { + var startTimeFormatted = "00:00"; + ImGui.InputText("start time", ref startTimeFormatted, 10); + } + ImGui.Unindent(); + ImGui.PopID(); + } +} \ No newline at end of file diff --git a/Plugin/Ui/SfxDebug.cs b/Plugin/Ui/SfxDebug.cs new file mode 100644 index 0000000..ed4526d --- /dev/null +++ b/Plugin/Ui/SfxDebug.cs @@ -0,0 +1,41 @@ +// This file is part of EngageTimer +// Copyright (C) 2023 Xorus +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using Dalamud.Interface; +using Dalamud.Interface.Windowing; +using ImGuiNET; + +namespace EngageTimer.Ui; + +public class SfxDebug : Window +{ + public SfxDebug() : base("Sound effect finder", ImGuiWindowFlags.AlwaysAutoResize) + { + IsOpen = true; + } + + private int _id = 0; + + public override void Draw() + { + if (ImGui.InputInt("ID", ref _id)) + { + Plugin.SfxPlay.SoundEffect((uint)_id); + } + + ImGui.SameLine(); + Components.IconButton(FontAwesomeIcon.Play, "Play", () => Plugin.SfxPlay.SoundEffect((uint)_id)); + } +} \ No newline at end of file