perf: Migrate NPC and Skill UI gumps to DynamicGump/StaticGump (#2414)

## Summary

Migrates five legacy `Gump`-derived UI dialogs in the NPC and Skill domains to the modern `DynamicGump` builder pipeline. All five gumps were chosen as `DynamicGump` rather than `StaticGump<T>` because their layout shape varies per instance, and several of them carry per-instance localization numbers (cliloc IDs) that the static cache cannot bake (see CLAUDE.md gump-system rule and `dev-docs/gump-system.md`).

Per-gump rationale:

- **`TownCrierGump` (`Mobiles/Townfolk/TownCrier.cs`)** - DynamicGump. Announcement count varies, expiration text is rebuilt per render via `ValueStringBuilder`, and one button per entry is emitted in a loop.
- **`ClaimListGump` (`Mobiles/Vendors/NPC/AnimalTrainer.cs`)** - DynamicGump. The pet list and resulting background/alpha-region heights vary per stabling player.
- **`AnimalLoreGump` (`Skills/AnimalLore.cs`)** - DynamicGump. Page count itself varies (3 pages pre-AOS, 5 pages on AOS) and several `AddHtmlLocalized` calls use cliloc IDs computed from per-creature data (loyalty rating `1049595 + c.Loyalty / 10`, food preference, pack instinct), which violates the StaticGump cliloc-bake rule.
- **`DisguiseGump` (`Items/Skill Items/Thief/DisguiseKit.cs`)** - DynamicGump. Page count and entry order shift on `from.Female`, `Body.IsFemale`, and `startAtHair`.
- **`CommentsGump` (`Gumps/CommentsGump.cs`)** - DynamicGump. Comment list and pagination depend on `Account.Comments` size; the title label encodes the variable account username string.

All five are now `Singleton => true`, have `private` constructors, and expose static `DisplayTo(...)` entry points that validate prerequisites before constructing - guaranteeing no empty gumps (CLAUDE.md Sec.13). All internal refresh paths (prompts, `OnDoubleClick`, command handlers, target callbacks) were updated to call `DisplayTo` rather than `new XGump(...)`. Legacy `m_`-prefixed fields renamed to `_camelCase` (CLAUDE.md Sec.12), and `DisguiseEntry`'s `m_`-prefixed public readonly fields converted to PascalCase auto-properties. `OnResponse` signatures updated to `in RelayInfo info`. No external callers needed updating - all `new XGump(...)` sites lived inside the same files.
This commit is contained in:
Kamron Batman 2026-04-25 19:51:42 -07:00 • committed by GitHub
parent ec85d97482
commit 50599e0453
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 342 additions and 279 deletions

View file

@ -62,60 +62,76 @@ namespace Server.SkillHandlers
}
else
{
from.SendGump(new AnimalLoreGump(c));
AnimalLoreGump.DisplayTo(from, c);
}
}
}
}
public class AnimalLoreGump : Gump
public class AnimalLoreGump : DynamicGump
{
private const int LabelColor = 0x24E5;
private readonly BaseCreature _creature;
public override bool Singleton => true;
public AnimalLoreGump(BaseCreature c) : base(250, 50)
private AnimalLoreGump(BaseCreature c) : base(250, 50) => _creature = c;
public static void DisplayTo(Mobile from, BaseCreature c)
{
AddPage(0);
if (from?.NetState == null || c == null || c.Deleted)
{
return;
}
AddImage(100, 100, 2080);
AddImage(118, 137, 2081);
AddImage(118, 207, 2081);
AddImage(118, 277, 2081);
AddImage(118, 347, 2083);
from.SendGump(new AnimalLoreGump(c));
}
AddHtml(147, 108, 210, 18, $"<center><i>{c.Name}</i></center>");
protected override void BuildLayout(ref DynamicGumpBuilder builder)
{
var c = _creature;
AddButton(240, 77, 2093, 2093, 2);
builder.AddPage(0);
AddImage(140, 138, 2091);
AddImage(140, 335, 2091);
builder.AddImage(100, 100, 2080);
builder.AddImage(118, 137, 2081);
builder.AddImage(118, 207, 2081);
builder.AddImage(118, 277, 2081);
builder.AddImage(118, 347, 2083);
builder.AddHtml(147, 108, 210, 18, $"<center><i>{c.Name}</i></center>");
builder.AddButton(240, 77, 2093, 2093, 2);
builder.AddImage(140, 138, 2091);
builder.AddImage(140, 335, 2091);
var pages = Core.AOS ? 5 : 3;
var page = 0;
AddPage(++page);
builder.AddPage(++page);
AddImage(128, 152, 2086);
AddHtmlLocalized(147, 150, 160, 18, 1049593, 200); // Attributes
builder.AddImage(128, 152, 2086);
builder.AddHtmlLocalized(147, 150, 160, 18, 1049593, 200); // Attributes
AddHtmlLocalized(153, 168, 160, 18, 1049578, LabelColor); // Hits
AddHtml(280, 168, 75, 18, FormatAttributes(c.Hits, c.HitsMax));
builder.AddHtmlLocalized(153, 168, 160, 18, 1049578, LabelColor); // Hits
builder.AddHtml(280, 168, 75, 18, FormatAttributes(c.Hits, c.HitsMax));
AddHtmlLocalized(153, 186, 160, 18, 1049579, LabelColor); // Stamina
AddHtml(280, 186, 75, 18, FormatAttributes(c.Stam, c.StamMax));
builder.AddHtmlLocalized(153, 186, 160, 18, 1049579, LabelColor); // Stamina
builder.AddHtml(280, 186, 75, 18, FormatAttributes(c.Stam, c.StamMax));
AddHtmlLocalized(153, 204, 160, 18, 1049580, LabelColor); // Mana
AddHtml(280, 204, 75, 18, FormatAttributes(c.Mana, c.ManaMax));
builder.AddHtmlLocalized(153, 204, 160, 18, 1049580, LabelColor); // Mana
builder.AddHtml(280, 204, 75, 18, FormatAttributes(c.Mana, c.ManaMax));
AddHtmlLocalized(153, 222, 160, 18, 1028335, LabelColor); // Strength
AddHtml(320, 222, 35, 18, FormatStat(c.Str));
builder.AddHtmlLocalized(153, 222, 160, 18, 1028335, LabelColor); // Strength
builder.AddHtml(320, 222, 35, 18, FormatStat(c.Str));
AddHtmlLocalized(153, 240, 160, 18, 3000113, LabelColor); // Dexterity
AddHtml(320, 240, 35, 18, FormatStat(c.Dex));
builder.AddHtmlLocalized(153, 240, 160, 18, 3000113, LabelColor); // Dexterity
builder.AddHtml(320, 240, 35, 18, FormatStat(c.Dex));
AddHtmlLocalized(153, 258, 160, 18, 3000112, LabelColor); // Intelligence
AddHtml(320, 258, 35, 18, FormatStat(c.Int));
builder.AddHtmlLocalized(153, 258, 160, 18, 3000112, LabelColor); // Intelligence
builder.AddHtml(320, 258, 35, 18, FormatStat(c.Int));
if (Core.AOS)
{
@ -129,17 +145,17 @@ namespace Server.SkillHandlers
bd = 0;
}
AddHtmlLocalized(153, 276, 160, 18, 1070793, LabelColor); // Barding Difficulty
AddHtml(320, y, 35, 18, FormatDouble(bd));
builder.AddHtmlLocalized(153, 276, 160, 18, 1070793, LabelColor); // Barding Difficulty
builder.AddHtml(320, y, 35, 18, FormatDouble(bd));
y += 18;
}
AddImage(128, y + 2, 2086);
AddHtmlLocalized(147, y, 160, 18, 1049594, 200); // Loyalty Rating
builder.AddImage(128, y + 2, 2086);
builder.AddHtmlLocalized(147, y, 160, 18, 1049594, 200); // Loyalty Rating
y += 18;
AddHtmlLocalized(
builder.AddHtmlLocalized(
153,
y,
160,
@ -150,122 +166,122 @@ namespace Server.SkillHandlers
}
else
{
AddImage(128, 278, 2086);
AddHtmlLocalized(147, 276, 160, 18, 3001016, 200); // Miscellaneous
builder.AddImage(128, 278, 2086);
builder.AddHtmlLocalized(147, 276, 160, 18, 3001016, 200); // Miscellaneous
AddHtmlLocalized(153, 294, 160, 18, 1049581, LabelColor); // Armor Rating
AddHtml(320, 294, 35, 18, FormatStat(c.VirtualArmor));
builder.AddHtmlLocalized(153, 294, 160, 18, 1049581, LabelColor); // Armor Rating
builder.AddHtml(320, 294, 35, 18, FormatStat(c.VirtualArmor));
}
AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, pages);
builder.AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
builder.AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, pages);
if (Core.AOS)
{
AddPage(++page);
builder.AddPage(++page);
AddImage(128, 152, 2086);
AddHtmlLocalized(147, 150, 160, 18, 1061645, 200); // Resistances
builder.AddImage(128, 152, 2086);
builder.AddHtmlLocalized(147, 150, 160, 18, 1061645, 200); // Resistances
AddHtmlLocalized(153, 168, 160, 18, 1061646, LabelColor); // Physical
AddHtml(320, 168, 35, 18, FormatElement(c.PhysicalResistance));
builder.AddHtmlLocalized(153, 168, 160, 18, 1061646, LabelColor); // Physical
builder.AddHtml(320, 168, 35, 18, FormatElement(c.PhysicalResistance));
AddHtmlLocalized(153, 186, 160, 18, 1061647, LabelColor); // Fire
AddHtml(320, 186, 35, 18, FormatElement(c.FireResistance, "#FF0000"));
builder.AddHtmlLocalized(153, 186, 160, 18, 1061647, LabelColor); // Fire
builder.AddHtml(320, 186, 35, 18, FormatElement(c.FireResistance, "#FF0000"));
AddHtmlLocalized(153, 204, 160, 18, 1061648, LabelColor); // Cold
AddHtml(320, 204, 35, 18, FormatElement(c.ColdResistance, "#000080"));
builder.AddHtmlLocalized(153, 204, 160, 18, 1061648, LabelColor); // Cold
builder.AddHtml(320, 204, 35, 18, FormatElement(c.ColdResistance, "#000080"));
AddHtmlLocalized(153, 222, 160, 18, 1061649, LabelColor); // Poison
AddHtml(320, 222, 35, 18, FormatElement(c.PoisonResistance, "#008000"));
builder.AddHtmlLocalized(153, 222, 160, 18, 1061649, LabelColor); // Poison
builder.AddHtml(320, 222, 35, 18, FormatElement(c.PoisonResistance, "#008000"));
AddHtmlLocalized(153, 240, 160, 18, 1061650, LabelColor); // Energy
AddHtml(320, 240, 35, 18, FormatElement(c.EnergyResistance, "#BF80FF"));
builder.AddHtmlLocalized(153, 240, 160, 18, 1061650, LabelColor); // Energy
builder.AddHtml(320, 240, 35, 18, FormatElement(c.EnergyResistance, "#BF80FF"));
AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
builder.AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
builder.AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
AddPage(++page);
builder.AddPage(++page);
AddImage(128, 152, 2086);
AddHtmlLocalized(147, 150, 160, 18, 1017319, 200); // Damage
builder.AddImage(128, 152, 2086);
builder.AddHtmlLocalized(147, 150, 160, 18, 1017319, 200); // Damage
AddHtmlLocalized(153, 168, 160, 18, 1061646, LabelColor); // Physical
AddHtml(320, 168, 35, 18, FormatElement(c.PhysicalDamage));
builder.AddHtmlLocalized(153, 168, 160, 18, 1061646, LabelColor); // Physical
builder.AddHtml(320, 168, 35, 18, FormatElement(c.PhysicalDamage));
AddHtmlLocalized(153, 186, 160, 18, 1061647, LabelColor); // Fire
AddHtml(320, 186, 35, 18, FormatElement(c.FireDamage, "#FF0000"));
builder.AddHtmlLocalized(153, 186, 160, 18, 1061647, LabelColor); // Fire
builder.AddHtml(320, 186, 35, 18, FormatElement(c.FireDamage, "#FF0000"));
AddHtmlLocalized(153, 204, 160, 18, 1061648, LabelColor); // Cold
AddHtml(320, 204, 35, 18, FormatElement(c.ColdDamage, "#000080"));
builder.AddHtmlLocalized(153, 204, 160, 18, 1061648, LabelColor); // Cold
builder.AddHtml(320, 204, 35, 18, FormatElement(c.ColdDamage, "#000080"));
AddHtmlLocalized(153, 222, 160, 18, 1061649, LabelColor); // Poison
AddHtml(320, 222, 35, 18, FormatElement(c.PoisonDamage, "#008000"));
builder.AddHtmlLocalized(153, 222, 160, 18, 1061649, LabelColor); // Poison
builder.AddHtml(320, 222, 35, 18, FormatElement(c.PoisonDamage, "#008000"));
AddHtmlLocalized(153, 240, 160, 18, 1061650, LabelColor); // Energy
AddHtml(320, 240, 35, 18, FormatElement(c.EnergyDamage, "#BF80FF"));
builder.AddHtmlLocalized(153, 240, 160, 18, 1061650, LabelColor); // Energy
builder.AddHtml(320, 240, 35, 18, FormatElement(c.EnergyDamage, "#BF80FF"));
if (Core.ML)
{
AddHtmlLocalized(153, 258, 160, 18, 1076750, LabelColor); // Base Damage
AddHtml(300, 258, 55, 18, FormatDamage(c.DamageMin, c.DamageMax));
builder.AddHtmlLocalized(153, 258, 160, 18, 1076750, LabelColor); // Base Damage
builder.AddHtml(300, 258, 55, 18, FormatDamage(c.DamageMin, c.DamageMax));
}
AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
builder.AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
builder.AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
}
AddPage(++page);
builder.AddPage(++page);
AddImage(128, 152, 2086);
AddHtmlLocalized(147, 150, 160, 18, 3001030, 200); // Combat Ratings
builder.AddImage(128, 152, 2086);
builder.AddHtmlLocalized(147, 150, 160, 18, 3001030, 200); // Combat Ratings
AddHtmlLocalized(153, 168, 160, 18, 1044103, LabelColor); // Wrestling
AddHtml(320, 168, 35, 18, FormatSkill(c, SkillName.Wrestling));
builder.AddHtmlLocalized(153, 168, 160, 18, 1044103, LabelColor); // Wrestling
builder.AddHtml(320, 168, 35, 18, FormatSkill(c, SkillName.Wrestling));
AddHtmlLocalized(153, 186, 160, 18, 1044087, LabelColor); // Tactics
AddHtml(320, 186, 35, 18, FormatSkill(c, SkillName.Tactics));
builder.AddHtmlLocalized(153, 186, 160, 18, 1044087, LabelColor); // Tactics
builder.AddHtml(320, 186, 35, 18, FormatSkill(c, SkillName.Tactics));
AddHtmlLocalized(153, 204, 160, 18, 1044086, LabelColor); // Magic Resistance
AddHtml(320, 204, 35, 18, FormatSkill(c, SkillName.MagicResist));
builder.AddHtmlLocalized(153, 204, 160, 18, 1044086, LabelColor); // Magic Resistance
builder.AddHtml(320, 204, 35, 18, FormatSkill(c, SkillName.MagicResist));
AddHtmlLocalized(153, 222, 160, 18, 1044061, LabelColor); // Anatomy
AddHtml(320, 222, 35, 18, FormatSkill(c, SkillName.Anatomy));
builder.AddHtmlLocalized(153, 222, 160, 18, 1044061, LabelColor); // Anatomy
builder.AddHtml(320, 222, 35, 18, FormatSkill(c, SkillName.Anatomy));
if (c is CuSidhe)
{
AddHtmlLocalized(153, 240, 160, 18, 1044077, LabelColor); // Healing
AddHtml(320, 240, 35, 18, FormatSkill(c, SkillName.Healing));
builder.AddHtmlLocalized(153, 240, 160, 18, 1044077, LabelColor); // Healing
builder.AddHtml(320, 240, 35, 18, FormatSkill(c, SkillName.Healing));
}
else
{
AddHtmlLocalized(153, 240, 160, 18, 1044090, LabelColor); // Poisoning
AddHtml(320, 240, 35, 18, FormatSkill(c, SkillName.Poisoning));
builder.AddHtmlLocalized(153, 240, 160, 18, 1044090, LabelColor); // Poisoning
builder.AddHtml(320, 240, 35, 18, FormatSkill(c, SkillName.Poisoning));
}
// TODO: Add remaining combat skills
AddImage(128, 260, 2086);
AddHtmlLocalized(147, 258, 160, 18, 3001032, 200); // Lore & Knowledge
builder.AddImage(128, 260, 2086);
builder.AddHtmlLocalized(147, 258, 160, 18, 3001032, 200); // Lore & Knowledge
AddHtmlLocalized(153, 276, 160, 18, 1044085, LabelColor); // Magery
AddHtml(320, 276, 35, 18, FormatSkill(c, SkillName.Magery));
builder.AddHtmlLocalized(153, 276, 160, 18, 1044085, LabelColor); // Magery
builder.AddHtml(320, 276, 35, 18, FormatSkill(c, SkillName.Magery));
AddHtmlLocalized(153, 294, 160, 18, 1044076, LabelColor); // Evaluating Intelligence
AddHtml(320, 294, 35, 18, FormatSkill(c, SkillName.EvalInt));
builder.AddHtmlLocalized(153, 294, 160, 18, 1044076, LabelColor); // Evaluating Intelligence
builder.AddHtml(320, 294, 35, 18, FormatSkill(c, SkillName.EvalInt));
AddHtmlLocalized(153, 312, 160, 18, 1044106, LabelColor); // Meditation
AddHtml(320, 312, 35, 18, FormatSkill(c, SkillName.Meditation));
builder.AddHtmlLocalized(153, 312, 160, 18, 1044106, LabelColor); // Meditation
builder.AddHtml(320, 312, 35, 18, FormatSkill(c, SkillName.Meditation));
// TODO: Add remaining skills
AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
builder.AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, page + 1);
builder.AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
AddPage(++page);
builder.AddPage(++page);
AddImage(128, 152, 2086);
AddHtmlLocalized(147, 150, 160, 18, 1049563, 200); // Preferred Foods
builder.AddImage(128, 152, 2086);
builder.AddHtmlLocalized(147, 150, 160, 18, 1049563, 200); // Preferred Foods
var foodPref = 3000340;
@ -296,10 +312,10 @@ namespace Server.SkillHandlers
// TODO: Add 1115752 "Blackrock Stew" as a food type
AddHtmlLocalized(153, 168, 160, 18, foodPref, LabelColor);
builder.AddHtmlLocalized(153, 168, 160, 18, foodPref, LabelColor);
AddImage(128, 188, 2086);
AddHtmlLocalized(147, 186, 160, 18, 1049569, 200); // Pack Instincts
builder.AddImage(128, 188, 2086);
builder.AddHtmlLocalized(147, 186, 160, 18, 1049569, 200); // Pack Instincts
var packInstinct = 3000340;
@ -336,7 +352,7 @@ namespace Server.SkillHandlers
packInstinct = 1049577; // Bull
}
AddHtmlLocalized(153, 204, 160, 18, packInstinct, LabelColor);
builder.AddHtmlLocalized(153, 204, 160, 18, packInstinct, LabelColor);
// TODO: Add Pet Slots
@ -344,10 +360,10 @@ namespace Server.SkillHandlers
if (!Core.AOS)
{
AddImage(128, 224, 2086);
AddHtmlLocalized(147, 222, 160, 18, 1049594, 200); // Loyalty Rating
builder.AddImage(128, 224, 2086);
builder.AddHtmlLocalized(147, 222, 160, 18, 1049594, 200); // Loyalty Rating
AddHtmlLocalized(
builder.AddHtmlLocalized(
153,
240,
160,
@ -357,8 +373,8 @@ namespace Server.SkillHandlers
);
}
AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, 1);
AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
builder.AddButton(340, 358, 5601, 5605, 0, GumpButtonType.Page, 1);
builder.AddButton(317, 358, 5603, 5607, 0, GumpButtonType.Page, page - 1);
}
private static string FormatSkill(BaseCreature c, SkillName name)