fix(combat): synchronize auto-target presentation
Make the retained toolbar consume retail's payload-free selection notice from canonical live state so a reentrant Auto Target replacement cannot be erased by the outer clear. Restrict automatic acquisition to the requested hostile non-player monster policy and record the intentional PK edge divergence. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
8be933fc94
commit
0f2d98c501
11 changed files with 225 additions and 7 deletions
|
|
@ -11964,7 +11964,7 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
private uint? GetSelectedOrClosestCombatTarget()
|
||||
{
|
||||
if (_selection.SelectedObjectId is { } selected && IsLiveCreatureTarget(selected))
|
||||
if (_selection.SelectedObjectId is { } selected && IsLiveHostileMonsterTarget(selected))
|
||||
return selected;
|
||||
|
||||
if (!_persistedGameplay.AutoTarget)
|
||||
|
|
@ -11984,7 +11984,7 @@ public sealed class GameWindow : IDisposable
|
|||
if (!_persistedGameplay.ViewCombatTarget
|
||||
|| !AcDream.Core.Combat.CombatInputPlanner.SupportsTargetedAttack(Combat.CurrentMode)
|
||||
|| _selection.SelectedObjectId is not uint selected
|
||||
|| !IsLiveCreatureTarget(selected)
|
||||
|| !IsLiveHostileMonsterTarget(selected)
|
||||
|| !_entitiesByServerGuid.TryGetValue(selected, out var target))
|
||||
return null;
|
||||
|
||||
|
|
@ -12496,7 +12496,7 @@ public sealed class GameWindow : IDisposable
|
|||
float bestDistanceSq = float.PositiveInfinity;
|
||||
foreach (var (guid, entity) in _entitiesByServerGuid)
|
||||
{
|
||||
if (!IsLiveCreatureTarget(guid))
|
||||
if (!IsLiveHostileMonsterTarget(guid))
|
||||
continue;
|
||||
|
||||
float distanceSq = System.Numerics.Vector3.DistanceSquared(
|
||||
|
|
@ -12545,6 +12545,17 @@ public sealed class GameWindow : IDisposable
|
|||
return (LiveItemType(guid) & AcDream.Core.Items.ItemType.Creature) != 0;
|
||||
}
|
||||
|
||||
private bool IsLiveHostileMonsterTarget(uint guid)
|
||||
{
|
||||
if (!IsLiveCreatureTarget(guid))
|
||||
return false;
|
||||
|
||||
return AcDream.Core.Combat.CombatTargetPolicy.IsHostileMonster(
|
||||
_playerServerGuid,
|
||||
Objects.Get(_playerServerGuid),
|
||||
Objects.Get(guid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the selected-object strip should show a Health meter for <paramref name="guid"/>.
|
||||
/// Exact projection of retail's <c>IsPlayer() || pet_owner ||
|
||||
|
|
|
|||
|
|
@ -437,7 +437,16 @@ public sealed class SelectedObjectController : IRetainedPanelController
|
|||
}
|
||||
|
||||
private void OnSelectionTransition(SelectionTransition transition)
|
||||
=> ApplySelection(transition.SelectedObjectId);
|
||||
{
|
||||
// Retail's CM_UI::SendNotice_SelectionChanged (0x00479F50) carries no
|
||||
// selected-id payload. gmToolbarUI::HandleSelectionChanged therefore
|
||||
// reads the live ACCWeenieObject::selectedID when its notice handler
|
||||
// runs. This matters when an earlier handler (ClientCombatSystem::
|
||||
// AutoTarget) selects a replacement reentrantly: the outer "cleared"
|
||||
// notice must render that replacement, not stale transition data.
|
||||
_ = transition;
|
||||
ApplySelection(_selection.SelectedObjectId);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
42
src/AcDream.Core/Combat/CombatTargetPolicy.cs
Normal file
42
src/AcDream.Core/Combat/CombatTargetPolicy.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using AcDream.Core.Items;
|
||||
|
||||
namespace AcDream.Core.Combat;
|
||||
|
||||
/// <summary>
|
||||
/// Eligibility policy for automatic monster acquisition.
|
||||
/// </summary>
|
||||
public static class CombatTargetPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true only for a live-data creature that is attackable, is not a
|
||||
/// player, and is not a pet. Friendly NPCs and non-creatures are rejected.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The attackability core is retail
|
||||
/// <c>ClientCombatSystem::ObjectIsAttackable @ 0x0056A600</c>. Retail
|
||||
/// <c>AutoTarget @ 0x0056BC80</c> falls back to
|
||||
/// <c>SelectNext(SELECTION_TYPE_COMPASS_ITEM)</c>, whose combat filter also
|
||||
/// uses ObjectIsAttackable. acdream deliberately narrows that set to
|
||||
/// non-player monsters for its automatic acquisition policy; explicit
|
||||
/// player-selection commands remain separate.
|
||||
/// </remarks>
|
||||
public static bool IsHostileMonster(
|
||||
uint playerId,
|
||||
ClientObject? player,
|
||||
ClientObject? candidate)
|
||||
{
|
||||
if (candidate is null
|
||||
|| candidate.ObjectId == playerId
|
||||
|| (candidate.Type & ItemType.Creature) == 0
|
||||
|| (candidate.PublicWeenieBitfield.GetValueOrDefault()
|
||||
& SelectedObjectHealthPolicy.BfPlayer) != 0
|
||||
|| candidate.PetOwnerId != 0)
|
||||
return false;
|
||||
|
||||
return SelectedObjectHealthPolicy.ObjectIsAttackable(
|
||||
playerId,
|
||||
player,
|
||||
candidate.ObjectId,
|
||||
candidate);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue