mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 04:13:40 +01:00
Add badges
Some checks are pending
Some checks are pending
This commit is contained in:
parent
5e4ad4a694
commit
efed9ca20b
9 changed files with 392 additions and 1 deletions
|
|
@ -495,6 +495,16 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
|||
#pragma warning restore SA1516
|
||||
#pragma warning restore SA1600
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of badge passwords used to unlock badges.
|
||||
/// </summary>
|
||||
public List<string> UsedBadgePasswords { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether badges should be shown on the title screen.
|
||||
/// </summary>
|
||||
public bool ShowBadgesOnTitleScreen { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Load a configuration from the provided path.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public enum DalamudAsset
|
|||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "troubleIcon.png")]
|
||||
TroubleIcon = 1006,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
||||
/// </summary>
|
||||
|
|
@ -124,6 +124,13 @@ public enum DalamudAsset
|
|||
[DalamudAssetPath("UIRes", "tsmShade.png")]
|
||||
TitleScreenMenuShade = 1013,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: Atlas containing badges.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "badgeAtlas.png")]
|
||||
BadgeAtlas = 1015,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ public enum SettingsOpenKind
|
|||
/// </summary>
|
||||
ServerInfoBar,
|
||||
|
||||
/// <summary>
|
||||
/// Open to the "Badges" page.
|
||||
/// </summary>
|
||||
Badge,
|
||||
|
||||
/// <summary>
|
||||
/// Open to the "Experimental" page.
|
||||
/// </summary>
|
||||
|
|
|
|||
48
Dalamud/Interface/Internal/Badge/BadgeInfo.cs
Normal file
48
Dalamud/Interface/Internal/Badge/BadgeInfo.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Badge;
|
||||
|
||||
/// <summary>
|
||||
/// Represents information about a badge.
|
||||
/// </summary>
|
||||
/// <param name="Name">Name of the badge.</param>
|
||||
/// <param name="Description">Description of the badge.</param>
|
||||
/// <param name="IconIndex">Icon index.</param>
|
||||
/// <param name="UnlockSha256">Sha256 hash of the unlock password.</param>
|
||||
/// <param name="UnlockMethod">How the badge is unlocked.</param>
|
||||
internal record BadgeInfo(
|
||||
Func<string> Name,
|
||||
Func<string> Description,
|
||||
int IconIndex,
|
||||
string UnlockSha256,
|
||||
BadgeUnlockMethod UnlockMethod)
|
||||
{
|
||||
private const float BadgeWidth = 256;
|
||||
private const float BadgeHeight = 256;
|
||||
private const float BadgesPerRow = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UV coordinates for the badge icon in the atlas.
|
||||
/// </summary>
|
||||
/// <param name="atlasWidthPx">Width of the atlas.</param>
|
||||
/// <param name="atlasHeightPx">Height of the atlas.</param>
|
||||
/// <returns>UV coordinates.</returns>
|
||||
public (Vector2 Uv0, Vector2 Uv1) GetIconUv(float atlasWidthPx, float atlasHeightPx)
|
||||
{
|
||||
// Calculate row and column from icon index
|
||||
var col = this.IconIndex % (int)BadgesPerRow;
|
||||
var row = this.IconIndex / (int)BadgesPerRow;
|
||||
|
||||
// Calculate pixel positions
|
||||
var x0 = col * BadgeWidth;
|
||||
var y0 = row * BadgeHeight;
|
||||
var x1 = x0 + BadgeWidth;
|
||||
var y1 = y0 + BadgeHeight;
|
||||
|
||||
// Convert to UV coordinates (0.0 to 1.0)
|
||||
var uv0 = new Vector2(x0 / atlasWidthPx, y0 / atlasHeightPx);
|
||||
var uv1 = new Vector2(x1 / atlasWidthPx, y1 / atlasHeightPx);
|
||||
|
||||
return (uv0, uv1);
|
||||
}
|
||||
}
|
||||
85
Dalamud/Interface/Internal/Badge/BadgeManager.cs
Normal file
85
Dalamud/Interface/Internal/Badge/BadgeManager.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Badge;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for managing user badges.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class BadgeManager : IServiceType
|
||||
{
|
||||
private readonly DalamudConfiguration configuration;
|
||||
|
||||
private readonly List<BadgeInfo> badges =
|
||||
[
|
||||
new(() => "Test Badge",
|
||||
() => "Awarded for testing badges.",
|
||||
0,
|
||||
"937e8d5fbb48bd4949536cd65b8d35c426b80d2f830c5c308e2cdec422ae2244",
|
||||
BadgeUnlockMethod.User),
|
||||
|
||||
new(() => "Fundraiser #1 Donor",
|
||||
() => "Awarded for participating in the first patch fundraiser.",
|
||||
1,
|
||||
"56e752257bd0cbb2944f95cc7b3cb3d0db15091dd043f7a195ed37028d079322",
|
||||
BadgeUnlockMethod.User)
|
||||
];
|
||||
|
||||
private readonly List<int> unlockedBadgeIndices = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BadgeManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">Configuration to use.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public BadgeManager(DalamudConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
|
||||
foreach (var usedBadge in this.configuration.UsedBadgePasswords)
|
||||
{
|
||||
this.TryUnlockBadge(usedBadge, BadgeUnlockMethod.Startup, out _);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the badges the user has unlocked.
|
||||
/// </summary>
|
||||
public IEnumerable<BadgeInfo> UnlockedBadges
|
||||
=> this.badges.Where((_, index) => this.unlockedBadgeIndices.Contains(index));
|
||||
|
||||
/// <summary>
|
||||
/// Unlock a badge with the given password and method.
|
||||
/// </summary>
|
||||
/// <param name="password">The password to unlock the badge with.</param>
|
||||
/// <param name="method">How we are unlocking this badge.</param>
|
||||
/// <param name="unlockedBadge">The badge that was unlocked, if the function returns true, null otherwise.</param>
|
||||
/// <returns>The unlocked badge, if one was unlocked by this call.</returns>
|
||||
public bool TryUnlockBadge(string password, BadgeUnlockMethod method, out BadgeInfo unlockedBadge)
|
||||
{
|
||||
var sha256 = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(password));
|
||||
var hashString = Convert.ToHexString(sha256);
|
||||
|
||||
foreach (var (idx, badge) in this.badges.Where(x => x.UnlockMethod == method || method == BadgeUnlockMethod.Startup).Index())
|
||||
{
|
||||
if (!this.unlockedBadgeIndices.Contains(idx) && badge.UnlockSha256.Equals(hashString, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (method != BadgeUnlockMethod.Startup)
|
||||
{
|
||||
this.configuration.UsedBadgePasswords.Add(password);
|
||||
this.configuration.QueueSave();
|
||||
}
|
||||
|
||||
this.unlockedBadgeIndices.Add(idx);
|
||||
unlockedBadge = badge;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
unlockedBadge = null!;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
22
Dalamud/Interface/Internal/Badge/BadgeUnlockMethod.cs
Normal file
22
Dalamud/Interface/Internal/Badge/BadgeUnlockMethod.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace Dalamud.Interface.Internal.Badge;
|
||||
|
||||
/// <summary>
|
||||
/// Method by which a badge can be unlocked.
|
||||
/// </summary>
|
||||
internal enum BadgeUnlockMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Badge can be unlocked by the user by entering a password.
|
||||
/// </summary>
|
||||
User,
|
||||
|
||||
/// <summary>
|
||||
/// Badge can be unlocked from Dalamud internal features.
|
||||
/// </summary>
|
||||
Internal,
|
||||
|
||||
/// <summary>
|
||||
/// Badge is no longer obtainable and can only be unlocked from the configuration file.
|
||||
/// </summary>
|
||||
Startup,
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@ using Dalamud.Game.Gui;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Animation.EasingFunctions;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.Badge;
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
using Dalamud.Interface.Internal.Windows.Data;
|
||||
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||
|
|
@ -540,6 +543,25 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
/// <param name="widget">Widget to set current.</param>
|
||||
public void SetDataWindowWidget(IDataWindowWidget widget) => this.dataWindow.CurrentWidget = widget;
|
||||
|
||||
/// <summary>
|
||||
/// Play an animation when a badge has been unlocked.
|
||||
/// </summary>
|
||||
/// <param name="badge">The badge that has been unlocked.</param>
|
||||
public void StartBadgeUnlockAnimation(BadgeInfo badge)
|
||||
{
|
||||
var badgeTexture = Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(DalamudAsset.BadgeAtlas);
|
||||
var uvs = badge.GetIconUv(badgeTexture.Width, badgeTexture.Height);
|
||||
|
||||
// TODO: Make it more fancy?
|
||||
Service<NotificationManager>.Get().AddNotification(
|
||||
new Notification
|
||||
{
|
||||
Title = "Badge unlocked!",
|
||||
Content = $"You unlocked the badge '{badge.Name()}'",
|
||||
Type = NotificationType.Success,
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDraw()
|
||||
{
|
||||
this.FrameCount++;
|
||||
|
|
@ -561,6 +583,7 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
{
|
||||
this.DrawHiddenDevMenuOpener();
|
||||
this.DrawDevMenu();
|
||||
this.DrawTitleScreenBadges();
|
||||
|
||||
if (Service<GameGui>.Get().GameUiHidden)
|
||||
return;
|
||||
|
|
@ -591,6 +614,68 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawTitleScreenBadges()
|
||||
{
|
||||
if (!this.titleScreenMenuWindow.IsOpen)
|
||||
return;
|
||||
|
||||
var badgeManager = Service<BadgeManager>.Get();
|
||||
if (!this.configuration.ShowBadgesOnTitleScreen || !badgeManager.UnlockedBadges.Any())
|
||||
return;
|
||||
|
||||
var vp = ImGui.GetMainViewport();
|
||||
ImGui.SetNextWindowPos(vp.Pos);
|
||||
ImGui.SetNextWindowSize(vp.Size);
|
||||
ImGuiHelpers.ForceNextWindowMainViewport();
|
||||
ImGui.SetNextWindowBgAlpha(0f);
|
||||
|
||||
ImGui.Begin(
|
||||
"###TitleScreenBadgeWindow"u8,
|
||||
ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags.NoNav);
|
||||
|
||||
var badgeAtlas = Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(DalamudAsset.BadgeAtlas);
|
||||
var badgeSize = ImGuiHelpers.GlobalScale * 80;
|
||||
var spacing = ImGuiHelpers.GlobalScale * 10;
|
||||
const float margin = 60f;
|
||||
var startPos = vp.Pos + new Vector2(vp.Size.X - margin, margin);
|
||||
|
||||
// Use the mouse position in screen space for hover detection because the usual ImGui hover checks
|
||||
// don't work with this full-viewport overlay window setup.
|
||||
var mouse = ImGui.GetMousePos();
|
||||
|
||||
foreach (var badge in badgeManager.UnlockedBadges)
|
||||
{
|
||||
var uvs = badge.GetIconUv(badgeAtlas.Width, badgeAtlas.Height);
|
||||
|
||||
startPos.X -= badgeSize;
|
||||
ImGui.SetCursorPos(startPos);
|
||||
ImGui.Image(badgeAtlas.Handle, new Vector2(badgeSize), uvs.Uv0, uvs.Uv1);
|
||||
|
||||
// Get the actual screen-space bounds of the image we just drew
|
||||
var badgeMin = ImGui.GetItemRectMin();
|
||||
var badgeMax = ImGui.GetItemRectMax();
|
||||
|
||||
// add spacing to the left for the next badge
|
||||
startPos.X -= spacing;
|
||||
|
||||
// Manual hit test using mouse position
|
||||
if (mouse.X >= badgeMin.X && mouse.X <= badgeMax.X && mouse.Y >= badgeMin.Y && mouse.Y <= badgeMax.Y)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.PushTextWrapPos(300 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextWrapped(badge.Name());
|
||||
ImGui.Separator();
|
||||
ImGui.TextColoredWrapped(ImGuiColors.DalamudGrey, badge.Description());
|
||||
ImGui.PopTextWrapPos();
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
|
||||
private void DrawCreditsDarkeningAnimation()
|
||||
{
|
||||
using var style1 = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding, 0f);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ internal sealed class SettingsWindow : Window
|
|||
new SettingsTabLook(),
|
||||
new SettingsTabAutoUpdates(),
|
||||
new SettingsTabDtr(),
|
||||
new SettingsTabBadge(),
|
||||
new SettingsTabExperimental(),
|
||||
new SettingsTabAbout()
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
using CheapLoc;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Internal.Badge;
|
||||
using Dalamud.Interface.Internal.Windows.Settings.Widgets;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility.Internal;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
|
||||
internal sealed class SettingsTabBadge : SettingsTab
|
||||
{
|
||||
private string badgePassword = string.Empty;
|
||||
private bool badgeWasError = false;
|
||||
|
||||
public override string Title => Loc.Localize("DalamudSettingsBadge", "Badges");
|
||||
|
||||
public override SettingsOpenKind Kind => SettingsOpenKind.ServerInfoBar;
|
||||
|
||||
public override SettingsEntry[] Entries { get; } =
|
||||
[
|
||||
new SettingsEntry<bool>(
|
||||
LazyLoc.Localize("DalamudSettingsShowBadgesOnTitleScreen", "Show Badges on Title Screen"),
|
||||
LazyLoc.Localize("DalamudSettingsShowBadgesOnTitleScreenHint", "If enabled, your unlocked badges will also be shown on the title screen."),
|
||||
c => c.ShowBadgesOnTitleScreen,
|
||||
(v, c) => c.ShowBadgesOnTitleScreen = v),
|
||||
];
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var badgeManager = Service<BadgeManager>.Get();
|
||||
var dalamudInterface = Service<DalamudInterface>.Get();
|
||||
|
||||
ImGui.TextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarHint", "Plugins can put additional information into your server information bar(where world & time can be seen).\nYou can reorder and disable these here."));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
ImGui.Text(Loc.Localize("DalamudSettingsBadgesUnlock", "Unlock a badge"));
|
||||
ImGui.TextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsBadgesUnlockHint", "If you have received a code for a badge, enter it here to unlock the badge.\nCodes are usually given out during community events or contests."));
|
||||
ImGui.InputTextWithHint(
|
||||
"##BadgePassword",
|
||||
Loc.Localize("DalamudSettingsBadgesUnlockHintInput", "Enter badge code here"),
|
||||
ref this.badgePassword,
|
||||
100);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(Loc.Localize("DalamudSettingsBadgesUnlockButton", "Unlock Badge")))
|
||||
{
|
||||
if (badgeManager.TryUnlockBadge(this.badgePassword.Trim(), BadgeUnlockMethod.User, out var unlockedBadge))
|
||||
{
|
||||
dalamudInterface.StartBadgeUnlockAnimation(unlockedBadge);
|
||||
this.badgeWasError = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.badgeWasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.badgeWasError)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingsBadgesUnlockError", "Failed to unlock badge. The code may be invalid or you may have already unlocked this badge."));
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
base.Draw();
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
var haveBadges = badgeManager.UnlockedBadges.ToArray();
|
||||
|
||||
if (haveBadges.Length == 0)
|
||||
{
|
||||
ImGui.TextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDidNone", "You did not unlock any badges yet.\nBadges can be unlocked by participating in community events or contests."));
|
||||
}
|
||||
|
||||
var badgeTexture = Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(DalamudAsset.BadgeAtlas);
|
||||
foreach (var badge in haveBadges)
|
||||
{
|
||||
var uvs = badge.GetIconUv(badgeTexture.Width, badgeTexture.Height);
|
||||
var sectionSize = ImGuiHelpers.GlobalScale * 66;
|
||||
|
||||
var startCursor = ImGui.GetCursorPos();
|
||||
|
||||
ImGui.SetCursorPos(startCursor);
|
||||
|
||||
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
||||
var cursorBeforeImage = ImGui.GetCursorPos();
|
||||
var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos();
|
||||
|
||||
if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize))
|
||||
{
|
||||
ImGui.Image(badgeTexture.Handle, iconSize, uvs.Uv0, uvs.Uv1);
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPos(cursorBeforeImage);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
ImGui.SameLine();
|
||||
|
||||
var cursor = ImGui.GetCursorPos();
|
||||
|
||||
// Name
|
||||
ImGui.Text(badge.Name());
|
||||
|
||||
cursor.Y += ImGui.GetTextLineHeightWithSpacing();
|
||||
ImGui.SetCursorPos(cursor);
|
||||
|
||||
// Description
|
||||
ImGui.TextWrapped(badge.Description());
|
||||
|
||||
startCursor.Y += sectionSize;
|
||||
ImGui.SetCursorPos(startCursor);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue