fix #234: port retail appraisal floaty and inscriptions

This commit is contained in:
Erik 2026-07-23 12:12:57 +02:00
parent 643cdfe66e
commit f1a7912160
23 changed files with 1310 additions and 106 deletions

View file

@ -22,6 +22,7 @@ public sealed class AppraisalUiController : IRetainedPanelController
public const uint ItemPanelId = 0x1000012Eu;
public const uint ItemTextId = 0x1000013Cu;
public const uint ItemScrollbarId = 0x1000013Du;
public const uint InscriptionBackgroundId = 0x10000137u;
public const uint InscriptionTextId = 0x1000013Eu;
public const uint SignatureTextId = 0x1000013Fu;
public const uint InscriptionScrollbarId = 0x1000046Eu;
@ -38,6 +39,9 @@ public sealed class AppraisalUiController : IRetainedPanelController
private readonly ItemInteractionController _interaction;
private readonly CombatState _combat;
private readonly Spellbook _spellbook;
private readonly Func<string> _playerName;
private readonly Action<uint, string> _sendSetInscription;
private readonly Action<string> _systemMessage;
private readonly Action _show;
private readonly UiElement _itemPanel;
private readonly UiElement _creaturePanel;
@ -47,6 +51,7 @@ public sealed class AppraisalUiController : IRetainedPanelController
private readonly UiText? _inscriptionText;
private readonly UiField? _inscriptionField;
private readonly UiText? _signature;
private readonly UiDatElement? _inscriptionBackground;
private readonly UiButton? _close;
private AppraisalView _activeView;
private uint _itemObjectId;
@ -56,6 +61,9 @@ public sealed class AppraisalUiController : IRetainedPanelController
private string _itemReport = string.Empty;
private string _inscriptionValue = string.Empty;
private string _signatureValue = string.Empty;
private string _scribeName = string.Empty;
private string _oldInscription = string.Empty;
private bool _presentationInscribable;
private double _refreshElapsed;
private bool _disposed;
@ -65,6 +73,9 @@ public sealed class AppraisalUiController : IRetainedPanelController
ItemInteractionController interaction,
CombatState combat,
Spellbook spellbook,
Func<string> playerName,
Action<uint, string> sendSetInscription,
Action<string> systemMessage,
Action show,
Action close,
UiElement itemPanel,
@ -77,6 +88,9 @@ public sealed class AppraisalUiController : IRetainedPanelController
_interaction = interaction;
_combat = combat;
_spellbook = spellbook;
_playerName = playerName;
_sendSetInscription = sendSetInscription;
_systemMessage = systemMessage;
_show = show;
_itemPanel = itemPanel;
_creaturePanel = creaturePanel;
@ -86,6 +100,8 @@ public sealed class AppraisalUiController : IRetainedPanelController
_inscriptionText = layout.FindElement(InscriptionTextId) as UiText;
_inscriptionField = layout.FindElement(InscriptionTextId) as UiField;
_signature = layout.FindElement(SignatureTextId) as UiText;
_inscriptionBackground =
layout.FindElement(InscriptionBackgroundId) as UiDatElement;
_close = layout.FindElement(CloseId) as UiButton;
_title.LinesProvider = () =>
@ -99,17 +115,26 @@ public sealed class AppraisalUiController : IRetainedPanelController
if (_inscriptionField is not null)
{
_inscriptionField.SetText(string.Empty);
// Retail conditionally enables this field only after its ownership
// and inscription-permission checks. That write transaction remains
// tracked by AP-110; keep the imported field display-only until the
// matching SetInscriptionEditableState/submit path is present.
_inscriptionField.Enabled = false;
_inscriptionField.ClearOnSubmit = false;
_inscriptionField.RecordHistory = false;
_inscriptionField.Editable = false;
_inscriptionField.Selectable = false;
_inscriptionField.OnFocusGained = HandleInscriptionGainingFocus;
_inscriptionField.OnFocusLost = HandleInscriptionLosingFocus;
_inscriptionField.OnReadOnlyClick = ReportInscriptionUnavailable;
if (layout.FindElement(InscriptionScrollbarId) is UiScrollbar scrollbar)
scrollbar.Model = _inscriptionField.Scroll;
}
if (_signature is not null)
_signature.LinesProvider = () =>
[new UiText.Line(_signatureValue, _signature.DefaultColor)];
if (_close is not null)
_close.OnClick = close;
if (_inscriptionBackground is not null)
{
_inscriptionBackground.OnClick = ReportInscriptionUnavailable;
_inscriptionBackground.ClickThrough = false;
}
SetActiveView(AppraisalView.Item);
}
@ -123,6 +148,9 @@ public sealed class AppraisalUiController : IRetainedPanelController
ItemInteractionController interaction,
CombatState combat,
Spellbook spellbook,
Func<string> playerName,
Action<uint, string> sendSetInscription,
Action<string> systemMessage,
Action show,
Action close)
{
@ -131,6 +159,9 @@ public sealed class AppraisalUiController : IRetainedPanelController
ArgumentNullException.ThrowIfNull(interaction);
ArgumentNullException.ThrowIfNull(combat);
ArgumentNullException.ThrowIfNull(spellbook);
ArgumentNullException.ThrowIfNull(playerName);
ArgumentNullException.ThrowIfNull(sendSetInscription);
ArgumentNullException.ThrowIfNull(systemMessage);
ArgumentNullException.ThrowIfNull(show);
ArgumentNullException.ThrowIfNull(close);
@ -146,6 +177,9 @@ public sealed class AppraisalUiController : IRetainedPanelController
interaction,
combat,
spellbook,
playerName,
sendSetInscription,
systemMessage,
show,
close,
itemPanel,
@ -228,7 +262,19 @@ public sealed class AppraisalUiController : IRetainedPanelController
_itemReport = string.Empty;
_inscriptionValue = string.Empty;
_inscriptionField?.SetText(string.Empty);
if (_inscriptionField is not null)
{
_inscriptionField.Editable = false;
_inscriptionField.Selectable = false;
_inscriptionField.Visible = false;
_inscriptionField.Scroll.SetScrollY(0);
}
_signatureValue = string.Empty;
if (_signature is not null)
_signature.Visible = false;
_scribeName = string.Empty;
_oldInscription = string.Empty;
_presentationInscribable = false;
_itemObjectId = 0;
_creatureObjectId = 0;
_characterObjectId = 0;
@ -246,19 +292,158 @@ public sealed class AppraisalUiController : IRetainedPanelController
obj,
appraisal,
ResolveSpellName);
_inscriptionValue = GetString(appraisal.Properties, 7u);
_inscriptionField?.SetText(_inscriptionValue);
string scribe = GetString(appraisal.Properties, 8u);
_signatureValue = string.IsNullOrWhiteSpace(scribe)
? string.Empty
: $"Inscribed by {scribe}";
SetInscription(obj, appraisal);
if (newlySelected)
{
_itemText.Scroll.SetScrollY(0);
_inscriptionText?.Scroll.SetScrollY(0);
_inscriptionField?.Scroll.SetScrollY(0);
}
}
/// <summary>
/// Port of <c>ItemExamineUI::SetInscription @ 0x004AE2F0</c> and
/// <c>SetInscriptionEditableState @ 0x004AC720</c>.
/// </summary>
private void SetInscription(
ClientObject obj,
AppraiseInfoParser.Parsed appraisal)
{
_oldInscription = string.Empty;
_scribeName = string.Empty;
_inscriptionValue = string.Empty;
_signatureValue = string.Empty;
var publicFlags =
(PublicWeenieFlags)(obj.PublicWeenieBitfield ?? 0u);
bool publicInscribable =
(publicFlags & PublicWeenieFlags.Inscribable) != 0;
_presentationInscribable = appraisal.HookProfile is { } hook
? (hook.Flags & 0x1u) != 0
: publicInscribable;
if (!_presentationInscribable)
{
if (_inscriptionField is not null)
{
_inscriptionField.SetText(string.Empty);
_inscriptionField.Editable = false;
_inscriptionField.Selectable = false;
_inscriptionField.Visible = false;
}
if (_inscriptionText is not null)
_inscriptionText.Visible = false;
if (_signature is not null)
_signature.Visible = false;
if (_inscriptionBackground is not null)
_inscriptionBackground.ClickThrough = false;
return;
}
_scribeName = GetString(appraisal.Properties, 8u);
_oldInscription = GetString(appraisal.Properties, 7u);
if (string.IsNullOrEmpty(_scribeName))
{
_inscriptionValue = "<Inscribe here>";
}
else if (!string.IsNullOrEmpty(_oldInscription))
{
_inscriptionValue = _oldInscription;
_signatureValue = $"--{_scribeName}";
}
if (_inscriptionField is not null)
{
_inscriptionField.SetText(_inscriptionValue);
_inscriptionField.Visible = true;
}
if (_inscriptionText is not null)
_inscriptionText.Visible = true;
if (_signature is not null)
_signature.Visible = true;
bool editable = publicInscribable
&& _interaction.IsOwnedByPlayer(obj.ObjectId)
&& (string.IsNullOrEmpty(_scribeName)
|| string.Equals(
_scribeName,
_playerName(),
StringComparison.OrdinalIgnoreCase));
if (_inscriptionField is not null)
{
_inscriptionField.Editable = editable;
_inscriptionField.Selectable = editable;
}
if (_inscriptionBackground is not null)
_inscriptionBackground.ClickThrough = editable;
}
private void HandleInscriptionGainingFocus()
{
if (_inscriptionField is not { Editable: true } field)
return;
if (string.IsNullOrEmpty(_scribeName))
{
field.SetText(string.Empty);
_inscriptionValue = string.Empty;
}
_signatureValue = $"--{_playerName()}";
}
private void HandleInscriptionLosingFocus(string text)
{
if (_itemObjectId == 0 || !_presentationInscribable)
return;
bool skipEmptyUnscribed = string.IsNullOrEmpty(text)
&& string.IsNullOrEmpty(_scribeName);
if (!skipEmptyUnscribed
&& !string.Equals(
text,
_oldInscription,
StringComparison.Ordinal))
{
_sendSetInscription(_itemObjectId, text);
}
if (string.IsNullOrEmpty(text))
{
_inscriptionValue = "<Inscribe here>";
_signatureValue = string.Empty;
_scribeName = string.Empty;
_inscriptionField?.SetText(_inscriptionValue);
}
else
{
_inscriptionValue = text;
_scribeName = _playerName();
_signatureValue = $"--{_scribeName}";
}
_oldInscription = text;
}
private void ReportInscriptionUnavailable()
{
if (_inscriptionField?.Editable == true)
return;
if (!string.IsNullOrEmpty(_scribeName)
&& !string.Equals(
_scribeName,
_playerName(),
StringComparison.OrdinalIgnoreCase))
{
_systemMessage($"Only {_scribeName} can change the inscription");
return;
}
if (_itemObjectId == 0
|| !_interaction.IsOwnedByPlayer(_itemObjectId))
{
_systemMessage("Item must be in your inventory to inscribe.");
return;
}
_systemMessage("This item is not inscribable.");
}
private void ApplyCreature(
ClientObject obj,
AppraiseInfoParser.Parsed appraisal,
@ -411,10 +596,20 @@ public sealed class AppraisalUiController : IRetainedPanelController
_disposed = true;
if (_close is not null)
_close.OnClick = null;
if (_inscriptionField is not null)
{
_inscriptionField.OnFocusGained = null;
_inscriptionField.OnFocusLost = null;
_inscriptionField.OnReadOnlyClick = null;
}
if (_inscriptionBackground is not null)
_inscriptionBackground.OnClick = null;
foreach (UiScrollbar scrollbar in Descendants(_layout.Root).OfType<UiScrollbar>())
if (ReferenceEquals(scrollbar.Model, _itemText.Scroll)
|| (_inscriptionText is not null
&& ReferenceEquals(scrollbar.Model, _inscriptionText.Scroll)))
&& ReferenceEquals(scrollbar.Model, _inscriptionText.Scroll))
|| (_inscriptionField is not null
&& ReferenceEquals(scrollbar.Model, _inscriptionField.Scroll)))
scrollbar.Model = null;
}
}