50 lines
1.8 KiB
C#
50 lines
1.8 KiB
C#
using AcDream.Core.Items;
|
|
|
|
namespace AcDream.Core.Spells;
|
|
|
|
public readonly record struct SpellTargetPolicyResult(
|
|
bool Allowed,
|
|
string? Message)
|
|
{
|
|
public static SpellTargetPolicyResult Accept { get; } =
|
|
new(true, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pure projection of
|
|
/// <c>ClientMagicSystem::ObjectCompatibleWithSpellTargetType</c>
|
|
/// (0x00567230). The caller owns target lookup and message presentation.
|
|
/// </summary>
|
|
public static class RetailSpellTargetPolicy
|
|
{
|
|
private const uint SpecialTargetMask = 0x00008107u;
|
|
|
|
public static SpellTargetPolicyResult Evaluate(
|
|
uint localPlayerId,
|
|
ClientObject target,
|
|
SpellMetadata spell)
|
|
{
|
|
uint mask = spell.TargetMask;
|
|
uint special = mask & SpecialTargetMask;
|
|
if (target.ObjectId == localPlayerId && special == 0u)
|
|
return new(false, "You cannot cast this spell upon yourself.");
|
|
if (target.StackSize > 1)
|
|
return new(false, "Cannot cast spell on a stack of items.");
|
|
|
|
if (((uint)target.Type & mask) == 0u && special == 0u)
|
|
return new(false, $"This spell cannot be cast on {target.Name}.");
|
|
|
|
// Retail joins the ordinary type match and the 0x8107 special-mask
|
|
// bypass before these two gates. IsPlayer is PWD bit 3; otherwise the
|
|
// object must carry BF_ATTACKABLE. Pets are never legal spell targets.
|
|
var flags = (PublicWeenieFlags)
|
|
target.PublicWeenieBitfield.GetValueOrDefault();
|
|
bool playerOrAttackable = (flags
|
|
& (PublicWeenieFlags.Player
|
|
| PublicWeenieFlags.Attackable)) != 0;
|
|
if (!playerOrAttackable || target.PetOwnerId != 0u)
|
|
return new(false, $"This spell cannot be cast on {target.Name}.");
|
|
|
|
return SpellTargetPolicyResult.Accept;
|
|
}
|
|
}
|