Add Donut Flavor Profile Display to Donut Editor (#4673)

* Add Donut Flavor Profile Display to Donut Editor

* Update profile on berry change
This commit is contained in:
RandomGuy 2025-12-29 16:20:54 -05:00 committed by GitHub
parent 870c10ea5e
commit 3ca4acd7d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 366 additions and 11 deletions

View File

@ -3200,6 +3200,16 @@ public class Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
public static System.Drawing.Bitmap flavorprofile {
get {
object obj = ResourceManager.GetObject("flavorprofile", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View File

@ -2665,4 +2665,7 @@
<data name="bitter_01_lv3" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\img\flavor power\bitter_01_lv3.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="flavorprofile" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\img\misc\flavorprofile.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

View File

@ -20,6 +20,7 @@ public static class DonutSpriteUtil
public static Bitmap? Sprite(this Donut9a donut) => GetDonutImage(donut);
public static Bitmap? StarSprite => (Bitmap?)Resources.ResourceManager.GetObject("star");
public static Bitmap? GetDonutFlavorImage(string donut) => (Bitmap?)Resources.ResourceManager.GetObject(donut);
public static Bitmap? GetFlavorProfileImage() => (Bitmap?)Resources.ResourceManager.GetObject("flavorprofile");
private static Bitmap? GetDonutImage(Donut9a donut)
{

View File

@ -0,0 +1,191 @@
using System.Drawing;
namespace PKHeX.WinForms
{
partial class DonutFlavorProfile9a
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
L_Spicy = new System.Windows.Forms.Label();
L_Sour = new System.Windows.Forms.Label();
L_Fresh = new System.Windows.Forms.Label();
L_Bitter = new System.Windows.Forms.Label();
L_Sweet = new System.Windows.Forms.Label();
L_SpicyStat = new System.Windows.Forms.Label();
L_SourStat = new System.Windows.Forms.Label();
L_FreshStat = new System.Windows.Forms.Label();
L_BitterStat = new System.Windows.Forms.Label();
L_SweetStat = new System.Windows.Forms.Label();
SuspendLayout();
//
// L_Spicy
//
L_Spicy.AutoSize = true;
L_Spicy.Font = new Font("Segoe UI", 10.8F, FontStyle.Bold);
L_Spicy.ForeColor = Color.FromArgb(255, 192, 192);
L_Spicy.Location = new Point(117, 18);
L_Spicy.Name = "L_Spicy";
L_Spicy.Size = new Size(45, 20);
L_Spicy.TabIndex = 0;
L_Spicy.Text = "Spicy";
//
// L_Sour
//
L_Sour.AutoSize = true;
L_Sour.Font = new Font("Segoe UI", 10.8F, FontStyle.Bold);
L_Sour.ForeColor = Color.FromArgb(255, 224, 192);
L_Sour.Location = new Point(197, 71);
L_Sour.Name = "L_Sour";
L_Sour.Size = new Size(41, 20);
L_Sour.TabIndex = 1;
L_Sour.Text = "Sour";
//
// L_Fresh
//
L_Fresh.AutoSize = true;
L_Fresh.Font = new Font("Segoe UI", 10.8F, FontStyle.Bold);
L_Fresh.ForeColor = Color.FromArgb(192, 255, 192);
L_Fresh.Location = new Point(181, 143);
L_Fresh.Name = "L_Fresh";
L_Fresh.Size = new Size(47, 20);
L_Fresh.TabIndex = 2;
L_Fresh.Text = "Fresh";
//
// L_Bitter
//
L_Bitter.AutoSize = true;
L_Bitter.Font = new Font("Segoe UI", 10.8F, FontStyle.Bold);
L_Bitter.ForeColor = Color.FromArgb(192, 192, 255);
L_Bitter.Location = new Point(47, 143);
L_Bitter.Name = "L_Bitter";
L_Bitter.Size = new Size(49, 20);
L_Bitter.TabIndex = 3;
L_Bitter.Text = "Bitter";
//
// L_Sweet
//
L_Sweet.AutoSize = true;
L_Sweet.Font = new Font("Segoe UI", 10.8F, FontStyle.Bold);
L_Sweet.ForeColor = Color.FromArgb(255, 192, 255);
L_Sweet.Location = new Point(37, 71);
L_Sweet.Name = "L_Sweet";
L_Sweet.Size = new Size(51, 20);
L_Sweet.TabIndex = 4;
L_Sweet.Text = "Sweet";
//
// L_SpicyStat
//
L_SpicyStat.AutoSize = true;
L_SpicyStat.Font = new Font("Segoe UI", 13.8F, FontStyle.Bold);
L_SpicyStat.ForeColor = Color.White;
L_SpicyStat.Location = new Point(117, 36);
L_SpicyStat.Name = "L_SpicyStat";
L_SpicyStat.Size = new Size(45, 25);
L_SpicyStat.TabIndex = 5;
L_SpicyStat.Text = "760";
//
// L_SourStat
//
L_SourStat.AutoSize = true;
L_SourStat.Font = new Font("Segoe UI", 13.8F, FontStyle.Bold);
L_SourStat.ForeColor = Color.White;
L_SourStat.Location = new Point(195, 88);
L_SourStat.Name = "L_SourStat";
L_SourStat.Size = new Size(45, 25);
L_SourStat.TabIndex = 6;
L_SourStat.Text = "760";
//
// L_FreshStat
//
L_FreshStat.AutoSize = true;
L_FreshStat.Font = new Font("Segoe UI", 13.8F, FontStyle.Bold);
L_FreshStat.ForeColor = Color.White;
L_FreshStat.Location = new Point(181, 160);
L_FreshStat.Name = "L_FreshStat";
L_FreshStat.Size = new Size(45, 25);
L_FreshStat.TabIndex = 7;
L_FreshStat.Text = "760";
//
// L_BitterStat
//
L_BitterStat.AutoSize = true;
L_BitterStat.Font = new Font("Segoe UI", 13.8F, FontStyle.Bold);
L_BitterStat.ForeColor = Color.White;
L_BitterStat.Location = new Point(49, 160);
L_BitterStat.Name = "L_BitterStat";
L_BitterStat.Size = new Size(45, 25);
L_BitterStat.TabIndex = 8;
L_BitterStat.Text = "760";
//
// L_SweetStat
//
L_SweetStat.AutoSize = true;
L_SweetStat.Font = new Font("Segoe UI", 13.8F, FontStyle.Bold);
L_SweetStat.ForeColor = Color.White;
L_SweetStat.Location = new Point(40, 88);
L_SweetStat.Name = "L_SweetStat";
L_SweetStat.Size = new Size(45, 25);
L_SweetStat.TabIndex = 9;
L_SweetStat.Text = "760";
//
// DonutFlavorProfile9a
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
BackColor = Color.Transparent;
BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
Controls.Add(L_Sour);
Controls.Add(L_Sweet);
Controls.Add(L_Spicy);
Controls.Add(L_SweetStat);
Controls.Add(L_BitterStat);
Controls.Add(L_FreshStat);
Controls.Add(L_SourStat);
Controls.Add(L_SpicyStat);
Controls.Add(L_Bitter);
Controls.Add(L_Fresh);
DoubleBuffered = true;
Name = "DonutFlavorProfile9a";
Size = new Size(277, 237);
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.Label L_Spicy;
private System.Windows.Forms.Label L_Sour;
private System.Windows.Forms.Label L_Fresh;
private System.Windows.Forms.Label L_Bitter;
private System.Windows.Forms.Label L_Sweet;
private System.Windows.Forms.Label L_SpicyStat;
private System.Windows.Forms.Label L_SourStat;
private System.Windows.Forms.Label L_FreshStat;
private System.Windows.Forms.Label L_BitterStat;
private System.Windows.Forms.Label L_SweetStat;
}
}

View File

@ -0,0 +1,127 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using PKHeX.Core;
namespace PKHeX.WinForms;
public partial class DonutFlavorProfile9a : UserControl
{
private const int MaxStatValue = 760;
private const int PentagonWidth = 110;
private const int PentagonHeight = 110;
private int ScaleValue;
private readonly int[] FlavorProfileStats = new int[5];
public DonutFlavorProfile9a() => InitializeComponent();
/// <summary>
/// Load stats from a Donut9a object
/// </summary>
public void LoadFromDonut(Donut9a donut)
{
Span<int> flavorStats = stackalloc int[5];
donut.RecalculateDonutFlavors(flavorStats);
flavorStats.CopyTo(FlavorProfileStats);
FlavorProfileStats[0] = flavorStats[0]; // Spicy - top
FlavorProfileStats[1] = flavorStats[4]; // Sour - top-right
FlavorProfileStats[2] = flavorStats[1]; // Fresh - bottom-right
FlavorProfileStats[3] = flavorStats[3]; // Bitter - bottom-left
FlavorProfileStats[4] = flavorStats[2]; // Sweet - top-left
int maxStat = 0;
foreach (var stat in FlavorProfileStats)
if (stat > maxStat) maxStat = stat;
ScaleValue = maxStat < 100 ? 500 : Math.Min(((maxStat + 99) / 100) * 100 + 100, MaxStatValue);
UpdateStatLabels(flavorStats);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
// Calculate pentagon area - centered in the control
var center = Size / 2;
// Scale pentagon size relative to control size
float scaleX = Width / 276f;
float scaleY = Height / 240f;
float radiusX = (PentagonWidth / 2f) * scaleX;
float radiusY = (PentagonHeight / 2f) * scaleY;
// Draw filled pentagon based on stats
DrawStatsPentagon(g, center, radiusX, radiusY);
}
private void DrawStatsPentagon(Graphics g, Size center, float radiusX, float radiusY)
{
// Calculate scaled points based on stat values with individual max per stat
Span<PointF> calculatedPoints = stackalloc PointF[5];
for (int i = 0; i < 5; i++)
{
// Calculate individual max for this stat
var point = GetStatCoordinate(radiusX, radiusY, FlavorProfileStats[i], i);
point += center;
calculatedPoints[i] = point;
}
// Reorder points for counter-clockwise drawing
Span<PointF> statPoints = stackalloc PointF[5];
ReorderCounterClockwise(statPoints, calculatedPoints);
// Fill the pentagon with semi-transparent yellow
using Brush fillBrush = new SolidBrush(Color.Yellow);
g.FillPolygon(fillBrush, statPoints);
}
private static PointF GetStatCoordinate(float radiusX, float radiusY, int statValue, int i)
{
int statMax = statValue switch
{
<= 350 => statValue + 200,
<= 700 => ((statValue + 99) / 100) * 100,
_ => MaxStatValue,
};
float scale = statMax > 0 ? Math.Min((float)statValue / statMax, 1.0f) : 0f;
// Use baseline scale (10%) if stat is 0, otherwise use calculated scale
const float baselineScale = 0.10f;
if (scale == 0f)
scale = baselineScale;
double angleStep = 2 * Math.PI / 5;
double angle = -Math.PI / 2 + i * angleStep;
float scaledRadiusX = radiusX * scale;
float scaledRadiusY = radiusY * scale;
return new PointF((float)(scaledRadiusX * Math.Cos(angle)), (float)(scaledRadiusY * Math.Sin(angle)));
}
private static void ReorderCounterClockwise(Span<PointF> statPoints, ReadOnlySpan<PointF> calculatedPoints)
{
ReadOnlySpan<int> counterClockwiseOrder = [0, 4, 3, 2, 1];
for (int i = 0; i < 5; i++)
statPoints[i] = calculatedPoints[counterClockwiseOrder[i]];
}
private void UpdateStatLabels(ReadOnlySpan<int> flavorStats)
{
L_SpicyStat.Text = flavorStats[0].ToString();
L_FreshStat.Text = flavorStats[1].ToString();
L_SweetStat.Text = flavorStats[2].ToString();
L_BitterStat.Text = flavorStats[3].ToString();
L_SourStat.Text = flavorStats[4].ToString();
}
}

View File

@ -41,13 +41,14 @@ private void InitializeComponent()
donutEditor = new DonutEditor9a();
B_Import = new System.Windows.Forms.Button();
B_Export = new System.Windows.Forms.Button();
DonutFlavorProfile = new DonutFlavorProfile9a();
modifyMenu.SuspendLayout();
SuspendLayout();
//
// B_Cancel
//
B_Cancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
B_Cancel.Location = new System.Drawing.Point(786, 348);
B_Cancel.Location = new System.Drawing.Point(786, 442);
B_Cancel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Cancel.Name = "B_Cancel";
B_Cancel.Size = new System.Drawing.Size(93, 27);
@ -64,14 +65,14 @@ private void InitializeComponent()
LB_Donut.Location = new System.Drawing.Point(14, 15);
LB_Donut.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
LB_Donut.Name = "LB_Donut";
LB_Donut.Size = new System.Drawing.Size(186, 361);
LB_Donut.Size = new System.Drawing.Size(186, 412);
LB_Donut.TabIndex = 2;
LB_Donut.SelectedIndexChanged += ChangeIndex;
//
// B_Save
//
B_Save.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
B_Save.Location = new System.Drawing.Point(786, 319);
B_Save.Location = new System.Drawing.Point(786, 413);
B_Save.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Save.Name = "B_Save";
B_Save.Size = new System.Drawing.Size(93, 27);
@ -111,7 +112,7 @@ private void InitializeComponent()
// B_ModifyAll
//
B_ModifyAll.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
B_ModifyAll.Location = new System.Drawing.Point(208, 349);
B_ModifyAll.Location = new System.Drawing.Point(208, 443);
B_ModifyAll.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_ModifyAll.Name = "B_ModifyAll";
B_ModifyAll.Size = new System.Drawing.Size(128, 27);
@ -123,7 +124,7 @@ private void InitializeComponent()
// B_Reset
//
B_Reset.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
B_Reset.Location = new System.Drawing.Point(548, 304);
B_Reset.Location = new System.Drawing.Point(344, 443);
B_Reset.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Reset.Name = "B_Reset";
B_Reset.Size = new System.Drawing.Size(128, 27);
@ -141,13 +142,13 @@ private void InitializeComponent()
donutEditor.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
donutEditor.Location = new System.Drawing.Point(207, 15);
donutEditor.Name = "donutEditor";
donutEditor.Size = new System.Drawing.Size(646, 240);
donutEditor.Size = new System.Drawing.Size(647, 240);
donutEditor.TabIndex = 27;
//
// B_Import
//
B_Import.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
B_Import.Location = new System.Drawing.Point(208, 304);
B_Import.Location = new System.Drawing.Point(208, 398);
B_Import.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Import.Name = "B_Import";
B_Import.Size = new System.Drawing.Size(128, 27);
@ -159,7 +160,7 @@ private void InitializeComponent()
// B_Export
//
B_Export.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
B_Export.Location = new System.Drawing.Point(344, 304);
B_Export.Location = new System.Drawing.Point(344, 398);
B_Export.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Export.Name = "B_Export";
B_Export.Size = new System.Drawing.Size(128, 27);
@ -168,11 +169,20 @@ private void InitializeComponent()
B_Export.UseVisualStyleBackColor = true;
B_Export.Click += B_Export_Click;
//
// DonutFlavorProfile
//
DonutFlavorProfile.BackColor = System.Drawing.Color.Transparent;
DonutFlavorProfile.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
DonutFlavorProfile.Location = new System.Drawing.Point(502, 243);
DonutFlavorProfile.Name = "DonutFlavorProfile";
DonutFlavorProfile.Size = new System.Drawing.Size(277, 237);
DonutFlavorProfile.TabIndex = 30;
//
// SAV_Donut9a
//
AllowDrop = true;
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
ClientSize = new System.Drawing.Size(894, 388);
ClientSize = new System.Drawing.Size(894, 482);
Controls.Add(B_Export);
Controls.Add(B_Import);
Controls.Add(donutEditor);
@ -181,6 +191,7 @@ private void InitializeComponent()
Controls.Add(B_Save);
Controls.Add(LB_Donut);
Controls.Add(B_Cancel);
Controls.Add(DonutFlavorProfile);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
Icon = Properties.Resources.Icon;
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
@ -208,5 +219,6 @@ private void InitializeComponent()
private DonutEditor9a donutEditor;
private System.Windows.Forms.Button B_Import;
private System.Windows.Forms.Button B_Export;
private DonutFlavorProfile9a DonutFlavorProfile;
}
}

View File

@ -3,6 +3,7 @@
using System.Text;
using System.Windows.Forms;
using PKHeX.Core;
using PKHeX.Drawing.Misc;
namespace PKHeX.WinForms;
@ -35,6 +36,8 @@ public SAV_Donut9a(SAV9ZA sav)
GetEntry(0);
AddDrop(this, LB_Donut, donutEditor);
DonutFlavorProfile.BackgroundImage = DonutSpriteUtil.GetFlavorProfileImage();
}
private void AddDrop(params ReadOnlySpan<Control> objects)
@ -81,12 +84,17 @@ private void Editor_ValueChanged(object? sender, EventArgs e)
return;
Loading = true;
// Only refresh the name in the list if it has changed.
var index = lastIndex;
var currentName = GetDonutName(index);
var donut = Donuts.GetDonut(index);
// Only refresh the name in the list if it has changed.
var currentName = GetDonutName(donut, index);
var existing = LB_Donut.Items[index];
if (existing.ToString() != currentName)
LB_Donut.Items[index] = currentName;
// Update profile if applicable
donutEditor.SaveDonut();
DonutFlavorProfile.LoadFromDonut(donut);
Loading = false;
}
@ -105,8 +113,11 @@ private void GetEntry(int index)
if (Loading || index < 0)
return;
Loading = true;
var donut = Donuts.GetDonut(index);
donutEditor.LoadDonut(donut);
DonutFlavorProfile.LoadFromDonut(donut);
Loading = false;
}
private void SetEntry(int index)