using AcDream.Content;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
namespace AcDream.App.UI.Layout;
///
/// #380 (Campaign OP gate 4, 2026-08-11): reads the Chat tab's two opacity-slider
/// row CAPTIONS ("Inactive Opacity" / "Active Opacity") + tooltips from the
/// installed DAT — retail UIOption::InqGameplayOptionNameAndTooltip
/// @0x004ef750's own catalog lookup, the mechanism
/// UIOption_Slider::SetGameplayOptionProperty @0x00485030 calls for
/// EVERY UIOption_Slider (both Chat opacity rows go through this path —
/// see gmChatOptionsUI::InitOptions's own SetGameplayOptionProperty
/// calls at 0x0049fcc0/0x0049fd1a). PlayerOptionPage::AddSliderOption
/// itself never sets a row caption (byte-verified — no StringInfo write in
/// its pseudo-C body), so the row's name-label child (element 0x1000021B,
/// shared by BOTH the unlabelled and range-captioned slider templates) is never
/// populated by AddSliderOption, nor is it baked statically into the
/// LayoutDesc (the SAME shared template element is reused for every slider on
/// every tab, so it cannot carry a fixed caption) —
/// never wired ANY source for it, which is #380's root cause.
///
///
/// Byte-verified recipe (a SECOND DBCache::GetDIDFromEnumStatic
/// sub-map lookup, sibling to 's own
/// (0x16, 2) defaults catalog): InqGameplayOptionNameAndTooltip's own
/// call site is GetDIDFromEnumStatic(&ret, 0x15, 2) — SAME master-map
/// key (2) as the defaults lookup, DIFFERENT sub-map key (0x15 vs
/// 0x16) — so 's own
/// (enumValue, enumCategory) order is (0x15, 2). Empirically
/// confirmed against the installed DATs: resolves to DID 0x78000000 (a
/// DIFFERENT object than the defaults catalog's 0x78000001 — the two
/// enum values genuinely name separate DAT objects, not two views of one). That
/// DBProperties has ONE top-level entry, property 0xD2, an
/// ArrayBaseProperty of StructBaseProperty entries — one per
/// registered GameplayOptionProperty in the whole game (not opacity-
/// specific). Each entry carries: 0xD4 = name StringInfo,
/// 0xD5 = tooltip StringInfo, 0xD6 = the
/// GameplayOptionProperty id this entry describes (an
/// EnumBaseProperty), 0xD7/0xD8 = unrelated floats. The
/// live array has exactly two entries, matching
/// (0xD6
/// = 0x10000080) and ActiveOpacityPropertyId (0xD6 =
/// 0x10000081) — resolving via table 0x2300000D (the SAME
/// TextFilter-family table #372 already established as this campaign's
/// runtime-string home, not the compiled-symbol table 0x23000003 the
/// section headers use) to "Inactive Opacity" /
/// "Active Opacity" — exactly the two labels the user's gate-4 report
/// named ("I also miss the text next to the bars for Inactive and Active
/// Opacity").
///
///
public static class ChatOptionsDatCaptions
{
/// DAT enum-category key for the FIRST (master-map) lookup — see
/// the class doc's byte-verified recipe (shared with
/// 's own defaults lookup).
private const uint EnumCategory = 2u;
/// DAT enum-value key for the SECOND (sub-map) lookup — the
/// name/tooltip catalog, NOT 's
/// 0x16 defaults catalog.
private const uint EnumValue = 0x15u;
private const uint CatalogArrayPropertyId = 0xD2u;
private const uint EntryNamePropertyId = 0xD4u;
private const uint EntryTooltipPropertyId = 0xD5u;
private const uint EntryOwningPropertyId = 0xD6u;
/// One resolved name/tooltip pair. Either half may be
/// if its own StringInfo failed to resolve —
/// the caller renders no text rather than inventing English (matching
/// this codebase's uniform degrade discipline).
public readonly record struct Caption(string? Name, string? Tooltip);
///
/// Resolves the Default/Active opacity sliders' row captions + tooltips
/// from the live DAT. Returns only when the
/// catalog DID itself does not resolve or is not a DBProperties —
/// a missing INDIVIDUAL entry still returns with
/// that pair's left at its default (both null).
///
public static bool TryRead(
IDatReaderWriter dats,
DatStringResolver strings,
out Caption defaultOpacity,
out Caption activeOpacity)
{
defaultOpacity = default;
activeOpacity = default;
uint did = RetailDataIdResolver.Resolve(dats, EnumValue, EnumCategory);
if (did == 0u || !dats.Portal.TryGet(did, out DBProperties? props) || props is null)
return false;
if (!props.Properties.TryGetValue(CatalogArrayPropertyId, out BaseProperty? arrayProp)
|| arrayProp is not ArrayBaseProperty array)
return false;
foreach (BaseProperty entry in array.Value)
{
if (entry is not StructBaseProperty entryStruct) continue;
if (!entryStruct.Value.TryGetValue(EntryOwningPropertyId, out BaseProperty? owningProp)
|| owningProp is not EnumBaseProperty owningEnum)
continue;
if (owningEnum.Value == ChatOptionsDatDefaults.DefaultOpacityPropertyId)
defaultOpacity = ReadCaption(entryStruct, strings);
else if (owningEnum.Value == ChatOptionsDatDefaults.ActiveOpacityPropertyId)
activeOpacity = ReadCaption(entryStruct, strings);
}
return true;
}
private static Caption ReadCaption(StructBaseProperty entry, DatStringResolver strings)
{
string? name = entry.Value.TryGetValue(EntryNamePropertyId, out BaseProperty? nameProp)
&& nameProp is StringInfoBaseProperty nameInfo
? strings.Resolve(nameInfo.Value.TableId.DataId, nameInfo.Value.StringId, nameInfo.Value.Token)
: null;
string? tooltip = entry.Value.TryGetValue(EntryTooltipPropertyId, out BaseProperty? tooltipProp)
&& tooltipProp is StringInfoBaseProperty tooltipInfo
? strings.Resolve(tooltipInfo.Value.TableId.DataId, tooltipInfo.Value.StringId, tooltipInfo.Value.Token)
: null;
return new Caption(name, tooltip);
}
}