mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-13 12:14:18 +01:00
250 lines
10 KiB
C#
250 lines
10 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using Dalamud.Data;
|
|
using Dalamud.Plugin;
|
|
using Lumina.Excel;
|
|
using Penumbra.GameData.Data;
|
|
using Penumbra.GameData.Enums;
|
|
using Penumbra.GameData.Structs;
|
|
using Race = Penumbra.GameData.Enums.Race;
|
|
|
|
namespace Glamourer.Services;
|
|
|
|
public class ItemManager : IDisposable
|
|
{
|
|
public const string Nothing = "Nothing";
|
|
public const string SmallClothesNpc = "Smallclothes (NPC)";
|
|
public const ushort SmallClothesNpcModel = 9903;
|
|
|
|
private readonly Configuration _config;
|
|
|
|
public readonly IdentifierService IdentifierService;
|
|
public readonly ExcelSheet<Lumina.Excel.GeneratedSheets.Item> ItemSheet;
|
|
public readonly StainData Stains;
|
|
public readonly ItemService ItemService;
|
|
public readonly RestrictedGear RestrictedGear;
|
|
|
|
public readonly EquipItem DefaultSword;
|
|
|
|
public ItemManager(Configuration config, DalamudPluginInterface pi, DataManager gameData, IdentifierService identifierService,
|
|
ItemService itemService)
|
|
{
|
|
_config = config;
|
|
ItemSheet = gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
|
IdentifierService = identifierService;
|
|
Stains = new StainData(pi, gameData, gameData.Language);
|
|
ItemService = itemService;
|
|
RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData);
|
|
DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Stains.Dispose();
|
|
RestrictedGear.Dispose();
|
|
}
|
|
|
|
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
|
|
=> _config.UseRestrictedGearProtection ? RestrictedGear.ResolveRestricted(armor, slot, race, gender) : (false, armor);
|
|
|
|
public static uint NothingId(EquipSlot slot)
|
|
=> uint.MaxValue - 128 - (uint)slot.ToSlot();
|
|
|
|
public static uint SmallclothesId(EquipSlot slot)
|
|
=> uint.MaxValue - 256 - (uint)slot.ToSlot();
|
|
|
|
public static uint NothingId(FullEquipType type)
|
|
=> uint.MaxValue - 384 - (uint)type;
|
|
|
|
public static EquipItem NothingItem(EquipSlot slot)
|
|
=> new(Nothing, NothingId(slot), 0, 0, 0, 0, slot.ToEquipType());
|
|
|
|
public static EquipItem NothingItem(FullEquipType type)
|
|
=> new(Nothing, NothingId(type), 0, 0, 0, 0, type);
|
|
|
|
public static EquipItem SmallClothesItem(EquipSlot slot)
|
|
=> new(SmallClothesNpc, SmallclothesId(slot), 0, SmallClothesNpcModel, 0, 1, slot.ToEquipType());
|
|
|
|
public EquipItem Resolve(EquipSlot slot, uint itemId)
|
|
{
|
|
slot = slot.ToSlot();
|
|
if (itemId == NothingId(slot))
|
|
return NothingItem(slot);
|
|
if (itemId == SmallclothesId(slot))
|
|
return SmallClothesItem(slot);
|
|
|
|
if (!ItemService.AwaitedService.TryGetValue(itemId, slot, out var item))
|
|
return EquipItem.FromId(itemId);
|
|
|
|
if (item.Type.ToSlot() != slot)
|
|
return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 0);
|
|
|
|
return item;
|
|
}
|
|
|
|
public EquipItem Resolve(FullEquipType type, uint itemId)
|
|
{
|
|
if (itemId == NothingId(type))
|
|
return NothingItem(type);
|
|
|
|
if (!ItemService.AwaitedService.TryGetValue(itemId, type is FullEquipType.Shield ? EquipSlot.MainHand : EquipSlot.OffHand, out var item))
|
|
return EquipItem.FromId(itemId);
|
|
|
|
if (item.Type != type)
|
|
return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 0);
|
|
|
|
return item;
|
|
}
|
|
|
|
public EquipItem Identify(EquipSlot slot, SetId id, byte variant)
|
|
{
|
|
slot = slot.ToSlot();
|
|
if (slot.ToIndex() == uint.MaxValue)
|
|
return new EquipItem($"Invalid ({id.Value}-{variant})", 0, 0, id, 0, variant, 0);
|
|
|
|
switch (id.Value)
|
|
{
|
|
case 0: return NothingItem(slot);
|
|
case SmallClothesNpcModel: return SmallClothesItem(slot);
|
|
default:
|
|
var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault();
|
|
return item.Valid
|
|
? item
|
|
: EquipItem.FromIds(0, 0, id, 0, variant, slot.ToEquipType());
|
|
}
|
|
}
|
|
|
|
/// <summary> Return the default offhand for a given mainhand, that is for both handed weapons, return the correct offhand part, and for everything else Nothing. </summary>
|
|
public EquipItem GetDefaultOffhand(EquipItem mainhand)
|
|
{
|
|
var offhandType = mainhand.Type.ValidOffhand();
|
|
if (offhandType.IsOffhandType())
|
|
return Resolve(offhandType, mainhand.ItemId);
|
|
|
|
return NothingItem(offhandType);
|
|
}
|
|
|
|
public EquipItem Identify(EquipSlot slot, SetId id, WeaponType type, byte variant, FullEquipType mainhandType = FullEquipType.Unknown)
|
|
{
|
|
if (slot is EquipSlot.OffHand)
|
|
{
|
|
var weaponType = mainhandType.ValidOffhand();
|
|
if (id.Value == 0)
|
|
return NothingItem(weaponType);
|
|
}
|
|
|
|
if (slot is not EquipSlot.MainHand and not EquipSlot.OffHand)
|
|
return new EquipItem($"Invalid ({id.Value}-{type.Value}-{variant})", 0, 0, id, type, variant, 0);
|
|
|
|
var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault();
|
|
return item.Valid
|
|
? item
|
|
: EquipItem.FromIds(0, 0, id, type, variant, slot.ToEquipType(), null);
|
|
}
|
|
|
|
/// <summary> Returns whether an item id represents a valid item for a slot and gives the item. </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
public bool IsItemValid(EquipSlot slot, uint itemId, out EquipItem item)
|
|
{
|
|
item = Resolve(slot, itemId);
|
|
return item.Valid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check whether an item id resolves to an existing item of the correct slot (which should not be weapons.)
|
|
/// The returned item is either the resolved correct item, or the Nothing item for that slot.
|
|
/// The return value is an empty string if there was no problem and a warning otherwise.
|
|
/// </summary>
|
|
public string ValidateItem(EquipSlot slot, ulong itemId, out EquipItem item, bool allowUnknown)
|
|
{
|
|
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
|
|
throw new Exception("Internal Error: Used armor functionality for weapons.");
|
|
|
|
if (itemId > uint.MaxValue)
|
|
{
|
|
var id = (SetId)(itemId & ushort.MaxValue);
|
|
var variant = (byte)(itemId >> 32);
|
|
item = Identify(slot, id, variant);
|
|
return allowUnknown ? string.Empty : $"The item {itemId} yields an unknown item.";
|
|
}
|
|
|
|
if (IsItemValid(slot, (uint)itemId, out item))
|
|
return string.Empty;
|
|
|
|
item = NothingItem(slot);
|
|
return $"The {slot.ToName()} item {itemId} does not exist, reset to Nothing.";
|
|
}
|
|
|
|
/// <summary> Returns whether a stain id is a valid stain. </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
public bool IsStainValid(StainId stain)
|
|
=> stain.Value == 0 || Stains.ContainsKey(stain);
|
|
|
|
/// <summary>
|
|
/// Check whether a stain id is an existing stain.
|
|
/// The returned stain id is either the input or 0.
|
|
/// The return value is an empty string if there was no problem and a warning otherwise.
|
|
/// </summary>
|
|
public string ValidateStain(StainId stain, out StainId ret, bool allowUnknown)
|
|
{
|
|
if (allowUnknown || IsStainValid(stain))
|
|
{
|
|
ret = stain;
|
|
return string.Empty;
|
|
}
|
|
|
|
ret = 0;
|
|
return $"The Stain {stain} does not exist, reset to unstained.";
|
|
}
|
|
|
|
/// <summary> Returns whether an offhand is valid given the required offhand type. </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
public bool IsOffhandValid(FullEquipType offType, uint offId, out EquipItem off)
|
|
{
|
|
off = Resolve(offType, offId);
|
|
return offType == FullEquipType.Unknown || off.Valid;
|
|
}
|
|
|
|
/// <summary> Returns whether an offhand is valid given mainhand. </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
public bool IsOffhandValid(in EquipItem main, uint offId, out EquipItem off)
|
|
=> IsOffhandValid(main.Type.ValidOffhand(), offId, out off);
|
|
|
|
/// <summary>
|
|
/// Check whether a combination of an item id for a mainhand and for an offhand is valid.
|
|
/// The returned items are either the resolved correct items,
|
|
/// the correct mainhand and an appropriate offhand (implicit offhand or nothing),
|
|
/// or the default sword and a nothing offhand.
|
|
/// The return value is an empty string if there was no problem and a warning otherwise.
|
|
/// </summary>
|
|
public string ValidateWeapons(uint mainId, uint offId, out EquipItem main, out EquipItem off)
|
|
{
|
|
var ret = string.Empty;
|
|
if (!IsItemValid(EquipSlot.MainHand, mainId, out main))
|
|
{
|
|
main = DefaultSword;
|
|
ret = $"The mainhand weapon {mainId} does not exist, reset to default sword.";
|
|
}
|
|
|
|
var offType = main.Type.ValidOffhand();
|
|
if (IsOffhandValid(offType, offId, out off))
|
|
return ret;
|
|
|
|
// Try implicit offhand.
|
|
// Can not be set to default sword before because then it could not be valid.
|
|
if (IsOffhandValid(offType, mainId, out off))
|
|
return $"The offhand weapon {offId} does not exist, reset to implied offhand.";
|
|
|
|
if (FullEquipTypeExtensions.OffhandTypes.Contains(offType))
|
|
{
|
|
main = DefaultSword;
|
|
off = NothingItem(FullEquipType.Shield);
|
|
return
|
|
$"The offhand weapon {offId} does not exist, but no default could be restored, reset mainhand to default sword and offhand to nothing.";
|
|
}
|
|
|
|
off = NothingItem(offType);
|
|
return ret.Length == 0 ? $"The offhand weapon {offId} does not exist, reset to no offhand." : ret;
|
|
}
|
|
}
|