fix(chargen): Campaign CC gate round 1 Batch G — real color wheel (DoColorSpots/DoGradDisk color rendering)
R2-5: retail's gmCGAppearancePage::DoColorSpots/SetSelection/DoGradDisk paint the nine color swatches and the gradient disc with a real, computed representative color (PalSet-averaged for Hair/Nose+Mouth+ Skin/Headgear/Shirt/Trousers/Footwear at fixed sample indices 0xd0/0xb0/0x520; direct-Palette for Eyes at 0x103), not the static authored art acdream showed before this batch. Ports the full palette-to-RGB pipeline: a new pure Core resolver (ChargenSwatchColorResolver + IChargenPaletteColorSource) backed by a new ChargenAppearanceCatalog.TryGetColor reading real Palette dat objects, pinned against the installed EoR dat. CharacterCreationAppearancePage recomputes all nine swatches + the gradient disc's tint on every refresh (part/color/heritage change) and paints them through a new ChargenSwatchColorTile overlay child — a flat-color-fill approximation of retail's actual recolored-sprite blit, since neither UiButton (sealed) nor UiDatElement exposes a per-instance sprite tint today. Two STOPPED items remain outside this batch's file contract before the mechanism is visually live: (1) wiring PalSetSource/ClothingTableSource/ PaletteColorSource from CharacterCreationUiController.cs (mirrors the existing PreviewControl seam); (2) a small additive Tint property on UiButton/UiDatElement for a byte-true recolor instead of the flat fill. Also ports Nose/Mouth/Skin's single non-interactive representative swatch, beyond AP-216/AP-217's original six-part scope. Register AP-216/AP-217 rewritten (not retired — the two STOPPED items keep them open). Tests: 11 new Core, 6 new Content live-DAT, 8 new App-layer fixture. App suite 5321/3 -> 5329/3, Runtime 1735/0 unchanged, zero regressions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2ad805469d
commit
834c2547a9
10 changed files with 1558 additions and 29 deletions
|
|
@ -2,6 +2,7 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Frozen;
|
||||
using AcDream.Core.CharGen;
|
||||
using DatClothingTable = DatReaderWriter.DBObjs.ClothingTable;
|
||||
using DatPalette = DatReaderWriter.DBObjs.Palette;
|
||||
using DatPalSet = DatReaderWriter.DBObjs.PalSet;
|
||||
using DatCloObjectEffect = DatReaderWriter.Types.CloObjectEffect;
|
||||
using DatCloSubPalette = DatReaderWriter.Types.CloSubPalette;
|
||||
|
|
@ -32,12 +33,24 @@ namespace AcDream.Content.CharGen;
|
|||
/// protect the CACHE from concurrent mutation — they do nothing for the
|
||||
/// underlying <c>DatCollection</c> read the cache miss triggers.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b><see cref="IChargenPaletteColorSource"/> (Campaign CC gate round 1
|
||||
/// Batch G, R2-5):</b> the real color-wheel/swatch mechanism
|
||||
/// (<c>ChargenSwatchColorResolver</c>) needs one more DAT read this class
|
||||
/// didn't previously do — a raw Palette dat object's (0x04......) own color
|
||||
/// table, retail's <c>Palette::get_color32</c> equivalent. Same lazy-cache
|
||||
/// shape as <see cref="TryGetPalSet"/>/<see cref="TryGetClothingTable"/>,
|
||||
/// same DAT-lock obligation on every call site.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class ChargenAppearanceCatalog : IChargenPalSetSource, IChargenClothingTableSource
|
||||
public sealed class ChargenAppearanceCatalog :
|
||||
IChargenPalSetSource, IChargenClothingTableSource, IChargenPaletteColorSource
|
||||
{
|
||||
private readonly IDatReaderWriter _dats;
|
||||
private readonly ConcurrentDictionary<uint, ChargenPalSet?> _palSets = new();
|
||||
private readonly ConcurrentDictionary<uint, ChargenClothingTable?> _clothingTables = new();
|
||||
private readonly ConcurrentDictionary<uint, DatPalette?> _palettes = new();
|
||||
|
||||
public ChargenAppearanceCatalog(IDatReaderWriter dats)
|
||||
{
|
||||
|
|
@ -50,6 +63,28 @@ public sealed class ChargenAppearanceCatalog : IChargenPalSetSource, IChargenClo
|
|||
public ChargenClothingTable? TryGetClothingTable(uint clothingTableId) =>
|
||||
_clothingTables.GetOrAdd(clothingTableId, LoadClothingTable);
|
||||
|
||||
/// <summary>
|
||||
/// Retail's <c>ClientCharGenState::GetColorFromPal @0x00563990</c>: load
|
||||
/// the Palette dat object and read its color table at a fixed index —
|
||||
/// direct <c>ARGB[index]</c>, no averaging, no shade indirection. Unlike
|
||||
/// retail's own unchecked array read, this bounds-checks
|
||||
/// <paramref name="index"/> against the loaded palette's actual color
|
||||
/// count and returns false rather than reading out of range (see
|
||||
/// <see cref="IChargenPaletteColorSource.TryGetColor"/>'s own doc for
|
||||
/// why that divergence is deliberate).
|
||||
/// </summary>
|
||||
public bool TryGetColor(uint paletteId, int index, out ChargenSwatchRgb color)
|
||||
{
|
||||
color = default;
|
||||
DatPalette? palette = _palettes.GetOrAdd(paletteId, id => _dats.Get<DatPalette>(id));
|
||||
if (palette is null || index < 0 || index >= palette.Colors.Count)
|
||||
return false;
|
||||
|
||||
DatReaderWriter.Types.ColorARGB c = palette.Colors[index];
|
||||
color = new ChargenSwatchRgb(c.Red, c.Green, c.Blue);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ChargenPalSet? LoadPalSet(uint id)
|
||||
{
|
||||
DatPalSet? palSet = _dats.Get<DatPalSet>(id);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue