punish v6.8.18.0
This commit is contained in:
commit
cfb4dea47e
316 changed files with 554088 additions and 0 deletions
48
LLib/LLib.GameData/EClassJob.cs
Normal file
48
LLib/LLib.GameData/EClassJob.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
namespace LLib.GameData;
|
||||
|
||||
public enum EClassJob : uint
|
||||
{
|
||||
Adventurer,
|
||||
Gladiator,
|
||||
Pugilist,
|
||||
Marauder,
|
||||
Lancer,
|
||||
Archer,
|
||||
Conjurer,
|
||||
Thaumaturge,
|
||||
Carpenter,
|
||||
Blacksmith,
|
||||
Armorer,
|
||||
Goldsmith,
|
||||
Leatherworker,
|
||||
Weaver,
|
||||
Alchemist,
|
||||
Culinarian,
|
||||
Miner,
|
||||
Botanist,
|
||||
Fisher,
|
||||
Paladin,
|
||||
Monk,
|
||||
Warrior,
|
||||
Dragoon,
|
||||
Bard,
|
||||
WhiteMage,
|
||||
BlackMage,
|
||||
Arcanist,
|
||||
Summoner,
|
||||
Scholar,
|
||||
Rogue,
|
||||
Ninja,
|
||||
Machinist,
|
||||
DarkKnight,
|
||||
Astrologian,
|
||||
Samurai,
|
||||
RedMage,
|
||||
BlueMage,
|
||||
Gunbreaker,
|
||||
Dancer,
|
||||
Reaper,
|
||||
Sage,
|
||||
Viper,
|
||||
Pictomancer
|
||||
}
|
188
LLib/LLib.GameData/EClassJobExtensions.cs
Normal file
188
LLib/LLib.GameData/EClassJobExtensions.cs
Normal file
|
@ -0,0 +1,188 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace LLib.GameData;
|
||||
|
||||
public static class EClassJobExtensions
|
||||
{
|
||||
public static bool IsClass(this EClassJob classJob)
|
||||
{
|
||||
bool flag;
|
||||
switch (classJob)
|
||||
{
|
||||
case EClassJob.Gladiator:
|
||||
case EClassJob.Pugilist:
|
||||
case EClassJob.Marauder:
|
||||
case EClassJob.Lancer:
|
||||
case EClassJob.Archer:
|
||||
case EClassJob.Conjurer:
|
||||
case EClassJob.Thaumaturge:
|
||||
case EClassJob.Arcanist:
|
||||
case EClassJob.Rogue:
|
||||
flag = true;
|
||||
break;
|
||||
default:
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
if (!flag && !classJob.IsCrafter())
|
||||
{
|
||||
return classJob.IsGatherer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool HasBaseClass(this EClassJob classJob)
|
||||
{
|
||||
return (from x in Enum.GetValues<EClassJob>()
|
||||
where x.IsClass()
|
||||
select x).Any((EClassJob x) => x.AsJob() == classJob);
|
||||
}
|
||||
|
||||
public static EClassJob AsJob(this EClassJob classJob)
|
||||
{
|
||||
return classJob switch
|
||||
{
|
||||
EClassJob.Gladiator => EClassJob.Paladin,
|
||||
EClassJob.Marauder => EClassJob.Warrior,
|
||||
EClassJob.Pugilist => EClassJob.Monk,
|
||||
EClassJob.Lancer => EClassJob.Dragoon,
|
||||
EClassJob.Rogue => EClassJob.Ninja,
|
||||
EClassJob.Archer => EClassJob.Bard,
|
||||
EClassJob.Conjurer => EClassJob.WhiteMage,
|
||||
EClassJob.Thaumaturge => EClassJob.BlackMage,
|
||||
EClassJob.Arcanist => EClassJob.Summoner,
|
||||
_ => classJob,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsTank(this EClassJob classJob)
|
||||
{
|
||||
switch (classJob)
|
||||
{
|
||||
case EClassJob.Gladiator:
|
||||
case EClassJob.Marauder:
|
||||
case EClassJob.Paladin:
|
||||
case EClassJob.Warrior:
|
||||
case EClassJob.DarkKnight:
|
||||
case EClassJob.Gunbreaker:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsHealer(this EClassJob classJob)
|
||||
{
|
||||
switch (classJob)
|
||||
{
|
||||
case EClassJob.Conjurer:
|
||||
case EClassJob.WhiteMage:
|
||||
case EClassJob.Scholar:
|
||||
case EClassJob.Astrologian:
|
||||
case EClassJob.Sage:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsMelee(this EClassJob classJob)
|
||||
{
|
||||
switch (classJob)
|
||||
{
|
||||
case EClassJob.Pugilist:
|
||||
case EClassJob.Lancer:
|
||||
case EClassJob.Monk:
|
||||
case EClassJob.Dragoon:
|
||||
case EClassJob.Rogue:
|
||||
case EClassJob.Ninja:
|
||||
case EClassJob.Samurai:
|
||||
case EClassJob.Reaper:
|
||||
case EClassJob.Viper:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPhysicalRanged(this EClassJob classJob)
|
||||
{
|
||||
switch (classJob)
|
||||
{
|
||||
case EClassJob.Archer:
|
||||
case EClassJob.Bard:
|
||||
case EClassJob.Machinist:
|
||||
case EClassJob.Dancer:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsCaster(this EClassJob classJob)
|
||||
{
|
||||
switch (classJob)
|
||||
{
|
||||
case EClassJob.Thaumaturge:
|
||||
case EClassJob.BlackMage:
|
||||
case EClassJob.Arcanist:
|
||||
case EClassJob.Summoner:
|
||||
case EClassJob.RedMage:
|
||||
case EClassJob.BlueMage:
|
||||
case EClassJob.Pictomancer:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DealsPhysicalDamage(this EClassJob classJob)
|
||||
{
|
||||
if (!classJob.IsTank() && !classJob.IsMelee())
|
||||
{
|
||||
return classJob.IsPhysicalRanged();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool DealsMagicDamage(this EClassJob classJob)
|
||||
{
|
||||
if (!classJob.IsHealer())
|
||||
{
|
||||
return classJob.IsCaster();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsCrafter(this EClassJob classJob)
|
||||
{
|
||||
if (classJob >= EClassJob.Carpenter)
|
||||
{
|
||||
return classJob <= EClassJob.Culinarian;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsGatherer(this EClassJob classJob)
|
||||
{
|
||||
if (classJob >= EClassJob.Miner)
|
||||
{
|
||||
return classJob <= EClassJob.Fisher;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string ToFriendlyString(this EClassJob classJob)
|
||||
{
|
||||
return classJob switch
|
||||
{
|
||||
EClassJob.WhiteMage => "White Mage",
|
||||
EClassJob.BlackMage => "Black Mage",
|
||||
EClassJob.DarkKnight => "Dark Knight",
|
||||
EClassJob.RedMage => "Red Mage",
|
||||
EClassJob.BlueMage => "Blue Mage",
|
||||
_ => classJob.ToString(),
|
||||
};
|
||||
}
|
||||
}
|
60
LLib/LLib.GameData/ETerritoryIntendedUse.cs
Normal file
60
LLib/LLib.GameData/ETerritoryIntendedUse.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
namespace LLib.GameData;
|
||||
|
||||
public enum ETerritoryIntendedUse : byte
|
||||
{
|
||||
CityArea = 0,
|
||||
OpenWorld = 1,
|
||||
Inn = 2,
|
||||
Dungeon = 3,
|
||||
VariantDungeon = 4,
|
||||
Gaol = 5,
|
||||
StartingArea = 6,
|
||||
QuestArea = 7,
|
||||
AllianceRaid = 8,
|
||||
QuestBattle = 9,
|
||||
Trial = 10,
|
||||
QuestArea2 = 12,
|
||||
ResidentialArea = 13,
|
||||
HousingInstances = 14,
|
||||
QuestArea3 = 15,
|
||||
Raid = 16,
|
||||
Raid2 = 17,
|
||||
Frontline = 18,
|
||||
ChocoboSquare = 20,
|
||||
RestorationEvent = 21,
|
||||
Sanctum = 22,
|
||||
GoldSaucer = 23,
|
||||
LordOfVerminion = 25,
|
||||
Diadem = 26,
|
||||
HallOfTheNovice = 27,
|
||||
CrystallineConflict = 28,
|
||||
QuestBattle2 = 29,
|
||||
Barracks = 30,
|
||||
DeepDungeon = 31,
|
||||
SeasonalEvent = 32,
|
||||
TreasureMapDuty = 33,
|
||||
SeasonalEventDuty = 34,
|
||||
Battlehall = 35,
|
||||
CrystallineConflict2 = 37,
|
||||
Diadem2 = 38,
|
||||
RivalWings = 39,
|
||||
Unknown1 = 40,
|
||||
Eureka = 41,
|
||||
SeasonalEvent2 = 43,
|
||||
LeapOfFaith = 44,
|
||||
MaskedCarnivale = 45,
|
||||
OceanFishing = 46,
|
||||
Diadem3 = 47,
|
||||
Bozja = 48,
|
||||
IslandSanctuary = 49,
|
||||
Battlehall2 = 50,
|
||||
Battlehall3 = 51,
|
||||
LargeScaleRaid = 52,
|
||||
LargeScaleSavageRaid = 53,
|
||||
QuestArea4 = 54,
|
||||
TribalInstance = 56,
|
||||
CriterionDuty = 57,
|
||||
CriterionSavageDuty = 58,
|
||||
Blunderville = 59,
|
||||
CosmicExploration = 60
|
||||
}
|
53
LLib/LLib.GameUI/LAddon.cs
Normal file
53
LLib/LLib.GameUI/LAddon.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.NativeWrapper;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace LLib.GameUI;
|
||||
|
||||
public static class LAddon
|
||||
{
|
||||
private const int UnitListCount = 18;
|
||||
|
||||
public unsafe static AtkUnitBase* GetAddonById(uint id)
|
||||
{
|
||||
AtkUnitList* ptr = &AtkStage.Instance()->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList;
|
||||
for (int i = 0; i < 18; i++)
|
||||
{
|
||||
AtkUnitList* ptr2 = ptr + i;
|
||||
foreach (int item in Enumerable.Range(0, Math.Min(ptr2->Count, ptr2->Entries.Length)))
|
||||
{
|
||||
AtkUnitBase* value = ptr2->Entries[item].Value;
|
||||
if (value != null && value->Id == id)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public unsafe static bool TryGetAddonByName<T>(this IGameGui gameGui, string addonName, out T* addonPtr) where T : unmanaged
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gameGui, "gameGui");
|
||||
ArgumentException.ThrowIfNullOrEmpty(addonName, "addonName");
|
||||
AtkUnitBasePtr addonByName = gameGui.GetAddonByName(addonName);
|
||||
if (!addonByName.IsNull)
|
||||
{
|
||||
addonPtr = (T*)addonByName.Address;
|
||||
return true;
|
||||
}
|
||||
addonPtr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public unsafe static bool IsAddonReady(AtkUnitBase* addon)
|
||||
{
|
||||
if (addon->IsVisible)
|
||||
{
|
||||
return addon->UldManager.LoadedState == AtkLoadState.Loaded;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
21
LLib/LLib.GameUI/LAtkValue.cs
Normal file
21
LLib/LLib.GameUI/LAtkValue.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace LLib.GameUI;
|
||||
|
||||
public static class LAtkValue
|
||||
{
|
||||
public unsafe static string? ReadAtkString(this AtkValue atkValue)
|
||||
{
|
||||
if (atkValue.Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Undefined)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (atkValue.String.HasValue)
|
||||
{
|
||||
return MemoryHelper.ReadSeStringNullTerminated(new IntPtr((byte*)atkValue.String)).WithCertainMacroCodeReplacements();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
16
LLib/LLib.GameUI/SeStringExtensions.cs
Normal file
16
LLib/LLib.GameUI/SeStringExtensions.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace LLib.GameUI;
|
||||
|
||||
public static class SeStringExtensions
|
||||
{
|
||||
public static string WithCertainMacroCodeReplacements(this SeString? str)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return new ReadOnlySeString(str.Encode()).WithCertainMacroCodeReplacements();
|
||||
}
|
||||
}
|
28
LLib/LLib.Gear/EBaseParam.cs
Normal file
28
LLib/LLib.Gear/EBaseParam.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
namespace LLib.Gear;
|
||||
|
||||
public enum EBaseParam : byte
|
||||
{
|
||||
None = 0,
|
||||
Strength = 1,
|
||||
Dexterity = 2,
|
||||
Vitality = 3,
|
||||
Intelligence = 4,
|
||||
Mind = 5,
|
||||
Piety = 6,
|
||||
GP = 10,
|
||||
CP = 11,
|
||||
DamagePhys = 12,
|
||||
DamageMag = 13,
|
||||
DefensePhys = 21,
|
||||
DefenseMag = 24,
|
||||
Tenacity = 19,
|
||||
Crit = 27,
|
||||
DirectHit = 22,
|
||||
Determination = 44,
|
||||
SpellSpeed = 46,
|
||||
SkillSpeed = 45,
|
||||
Craftsmanship = 70,
|
||||
Control = 71,
|
||||
Gathering = 72,
|
||||
Perception = 73
|
||||
}
|
72
LLib/LLib.Gear/EquipmentStats.cs
Normal file
72
LLib/LLib.Gear/EquipmentStats.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LLib.Gear;
|
||||
|
||||
public sealed record EquipmentStats(Dictionary<EBaseParam, StatInfo> Stats, byte MateriaCount)
|
||||
{
|
||||
private sealed class KeyValuePairComparer : IEqualityComparer<KeyValuePair<EBaseParam, StatInfo>>
|
||||
{
|
||||
public bool Equals(KeyValuePair<EBaseParam, StatInfo> x, KeyValuePair<EBaseParam, StatInfo> y)
|
||||
{
|
||||
if (x.Key == y.Key)
|
||||
{
|
||||
return object.Equals(x.Value, y.Value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetHashCode(KeyValuePair<EBaseParam, StatInfo> obj)
|
||||
{
|
||||
return HashCode.Combine((int)obj.Key, obj.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public short Get(EBaseParam param)
|
||||
{
|
||||
return (short)(GetEquipment(param) + GetMateria(param));
|
||||
}
|
||||
|
||||
public short GetEquipment(EBaseParam param)
|
||||
{
|
||||
Stats.TryGetValue(param, out StatInfo value);
|
||||
return value?.EquipmentValue ?? 0;
|
||||
}
|
||||
|
||||
public short GetMateria(EBaseParam param)
|
||||
{
|
||||
Stats.TryGetValue(param, out StatInfo value);
|
||||
return value?.MateriaValue ?? 0;
|
||||
}
|
||||
|
||||
public bool IsOvercapped(EBaseParam param)
|
||||
{
|
||||
Stats.TryGetValue(param, out StatInfo value);
|
||||
return value?.Overcapped ?? false;
|
||||
}
|
||||
|
||||
public bool Has(EBaseParam substat)
|
||||
{
|
||||
return Stats.ContainsKey(substat);
|
||||
}
|
||||
|
||||
public bool HasMateria()
|
||||
{
|
||||
return Stats.Values.Any((StatInfo x) => x.MateriaValue > 0);
|
||||
}
|
||||
|
||||
public bool Equals(EquipmentStats? other)
|
||||
{
|
||||
if (other != null && MateriaCount == other.MateriaCount)
|
||||
{
|
||||
return Stats.SequenceEqual(other.Stats, new KeyValuePairComparer());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(MateriaCount, Stats);
|
||||
}
|
||||
}
|
37
LLib/LLib.Gear/ExtendedBaseParam.cs
Normal file
37
LLib/LLib.Gear/ExtendedBaseParam.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace LLib.Gear;
|
||||
|
||||
[Sheet("BaseParam")]
|
||||
public readonly struct ExtendedBaseParam : IExcelRow<ExtendedBaseParam>
|
||||
{
|
||||
private const int ParamCount = 23;
|
||||
|
||||
public uint RowId => _003Crow_003EP;
|
||||
|
||||
public BaseParam BaseParam => new BaseParam(_003Cpage_003EP, _003Coffset_003EP, _003Crow_003EP);
|
||||
|
||||
public unsafe Collection<ushort> EquipSlotCategoryPct => new Collection<ushort>(_003Cpage_003EP, _003Coffset_003EP, _003Coffset_003EP, (delegate*<ExcelPage, uint, uint, uint, ushort>)(&EquipSlotCategoryPctCtor), 23);
|
||||
|
||||
public ExtendedBaseParam(ExcelPage page, uint offset, uint row)
|
||||
{
|
||||
_003Cpage_003EP = page;
|
||||
_003Coffset_003EP = offset;
|
||||
_003Crow_003EP = row;
|
||||
}
|
||||
|
||||
private static ushort EquipSlotCategoryPctCtor(ExcelPage page, uint parentOffset, uint offset, uint i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
return page.ReadUInt16(offset + 8 + (i - 1) * 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static ExtendedBaseParam Create(ExcelPage page, uint offset, uint row)
|
||||
{
|
||||
return new ExtendedBaseParam(page, offset, row);
|
||||
}
|
||||
}
|
219
LLib/LLib.Gear/GearStatsCalculator.cs
Normal file
219
LLib/LLib.Gear/GearStatsCalculator.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace LLib.Gear;
|
||||
|
||||
public sealed class GearStatsCalculator
|
||||
{
|
||||
private sealed record MateriaInfo(EBaseParam BaseParam, Collection<short> Values, bool HasItem);
|
||||
|
||||
private const uint EternityRingItemId = 8575u;
|
||||
|
||||
private static readonly uint[] CanHaveOffhand = new uint[14]
|
||||
{
|
||||
2u, 6u, 8u, 12u, 14u, 16u, 18u, 20u, 22u, 24u,
|
||||
26u, 28u, 30u, 32u
|
||||
};
|
||||
|
||||
private readonly ExcelSheet<Item> _itemSheet;
|
||||
|
||||
private readonly Dictionary<(uint ItemLevel, EBaseParam BaseParam), ushort> _itemLevelStatCaps = new Dictionary<(uint, EBaseParam), ushort>();
|
||||
|
||||
private readonly Dictionary<(EBaseParam BaseParam, int EquipSlotCategory), ushort> _equipSlotCategoryPct;
|
||||
|
||||
private readonly Dictionary<uint, MateriaInfo> _materiaStats;
|
||||
|
||||
public GearStatsCalculator(IDataManager? dataManager)
|
||||
: this(dataManager?.GetExcelSheet<ItemLevel>() ?? throw new ArgumentNullException("dataManager"), dataManager.GetExcelSheet<ExtendedBaseParam>(), dataManager.GetExcelSheet<Materia>(), dataManager.GetExcelSheet<Item>())
|
||||
{
|
||||
}
|
||||
|
||||
public GearStatsCalculator(ExcelSheet<ItemLevel> itemLevelSheet, ExcelSheet<ExtendedBaseParam> baseParamSheet, ExcelSheet<Materia> materiaSheet, ExcelSheet<Item> itemSheet)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(itemLevelSheet, "itemLevelSheet");
|
||||
ArgumentNullException.ThrowIfNull(baseParamSheet, "baseParamSheet");
|
||||
ArgumentNullException.ThrowIfNull(materiaSheet, "materiaSheet");
|
||||
ArgumentNullException.ThrowIfNull(itemSheet, "itemSheet");
|
||||
_itemSheet = itemSheet;
|
||||
foreach (ItemLevel item in itemLevelSheet)
|
||||
{
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Strength)] = item.Strength;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Dexterity)] = item.Dexterity;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Vitality)] = item.Vitality;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Intelligence)] = item.Intelligence;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Mind)] = item.Mind;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Piety)] = item.Piety;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.GP)] = item.GP;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.CP)] = item.CP;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.DamagePhys)] = item.PhysicalDamage;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.DamageMag)] = item.MagicalDamage;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.DefensePhys)] = item.Defense;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.DefenseMag)] = item.MagicDefense;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Tenacity)] = item.Tenacity;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Crit)] = item.CriticalHit;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.DirectHit)] = item.DirectHitRate;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Determination)] = item.Determination;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.SpellSpeed)] = item.SpellSpeed;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.SkillSpeed)] = item.SkillSpeed;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Gathering)] = item.Gathering;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Perception)] = item.Perception;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Craftsmanship)] = item.Craftsmanship;
|
||||
_itemLevelStatCaps[(item.RowId, EBaseParam.Control)] = item.Control;
|
||||
}
|
||||
_equipSlotCategoryPct = baseParamSheet.SelectMany((ExtendedBaseParam x) => from y in Enumerable.Range(0, x.EquipSlotCategoryPct.Count)
|
||||
select ((EBaseParam)x.RowId, y: y, x.EquipSlotCategoryPct[y])).ToDictionary(((EBaseParam, int y, ushort) x) => (x.Item1, x.y), ((EBaseParam, int y, ushort) x) => x.Item3);
|
||||
_materiaStats = materiaSheet.Where((Materia x) => x.RowId != 0 && x.BaseParam.RowId != 0).ToDictionary((Materia x) => x.RowId, (Materia x) => new MateriaInfo((EBaseParam)x.BaseParam.RowId, x.Value, x.Item[0].RowId != 0));
|
||||
}
|
||||
|
||||
public unsafe EquipmentStats CalculateGearStats(InventoryItem* item)
|
||||
{
|
||||
List<(uint, byte)> list = new List<(uint, byte)>();
|
||||
byte b = 0;
|
||||
if (item->ItemId != 8575)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ushort num = item->Materia[i];
|
||||
if (num != 0)
|
||||
{
|
||||
b++;
|
||||
list.Add((num, item->MateriaGrades[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return CalculateGearStats(_itemSheet.GetRow(item->ItemId), item->Flags.HasFlag(InventoryItem.ItemFlags.HighQuality), list)with
|
||||
{
|
||||
MateriaCount = b
|
||||
};
|
||||
}
|
||||
|
||||
public EquipmentStats CalculateGearStats(Item item, bool highQuality, IReadOnlyList<(uint MateriaId, byte Grade)> materias)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(materias, "materias");
|
||||
Dictionary<EBaseParam, StatInfo> dictionary = new Dictionary<EBaseParam, StatInfo>();
|
||||
for (int i = 0; i < item.BaseParam.Count; i++)
|
||||
{
|
||||
AddEquipmentStat(dictionary, item.BaseParam[i], item.BaseParamValue[i]);
|
||||
}
|
||||
if (highQuality)
|
||||
{
|
||||
for (int j = 0; j < item.BaseParamSpecial.Count; j++)
|
||||
{
|
||||
AddEquipmentStat(dictionary, item.BaseParamSpecial[j], item.BaseParamValueSpecial[j]);
|
||||
}
|
||||
}
|
||||
foreach (var materia in materias)
|
||||
{
|
||||
if (_materiaStats.TryGetValue(materia.MateriaId, out MateriaInfo value))
|
||||
{
|
||||
AddMateriaStat(item, dictionary, value, materia.Grade);
|
||||
}
|
||||
}
|
||||
return new EquipmentStats(dictionary, 0);
|
||||
}
|
||||
|
||||
private static void AddEquipmentStat(Dictionary<EBaseParam, StatInfo> result, RowRef<BaseParam> baseParam, short value)
|
||||
{
|
||||
if (baseParam.RowId != 0)
|
||||
{
|
||||
if (result.TryGetValue((EBaseParam)baseParam.RowId, out StatInfo value2))
|
||||
{
|
||||
result[(EBaseParam)baseParam.RowId] = value2 with
|
||||
{
|
||||
EquipmentValue = (short)(value2.EquipmentValue + value)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result[(EBaseParam)baseParam.RowId] = new StatInfo(value, 0, Overcapped: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddMateriaStat(Item item, Dictionary<EBaseParam, StatInfo> result, MateriaInfo materiaInfo, short grade)
|
||||
{
|
||||
if (!result.TryGetValue(materiaInfo.BaseParam, out StatInfo value))
|
||||
{
|
||||
value = (result[materiaInfo.BaseParam] = new StatInfo(0, 0, Overcapped: false));
|
||||
}
|
||||
if (materiaInfo.HasItem)
|
||||
{
|
||||
short num = (short)(GetMaximumStatValue(item, materiaInfo.BaseParam) - value.EquipmentValue);
|
||||
if (value.MateriaValue + materiaInfo.Values[grade] > num)
|
||||
{
|
||||
result[materiaInfo.BaseParam] = value with
|
||||
{
|
||||
MateriaValue = num,
|
||||
Overcapped = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result[materiaInfo.BaseParam] = value with
|
||||
{
|
||||
MateriaValue = (short)(value.MateriaValue + materiaInfo.Values[grade])
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result[materiaInfo.BaseParam] = value with
|
||||
{
|
||||
MateriaValue = (short)(value.MateriaValue + materiaInfo.Values[grade])
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public short GetMaximumStatValue(Item item, EBaseParam baseParamValue)
|
||||
{
|
||||
if (_itemLevelStatCaps.TryGetValue((item.LevelItem.RowId, baseParamValue), out var value))
|
||||
{
|
||||
return (short)Math.Round((float)(value * _equipSlotCategoryPct[(baseParamValue, (int)item.EquipSlotCategory.RowId)]) / 1000f, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public unsafe short CalculateAverageItemLevel(InventoryContainer* container)
|
||||
{
|
||||
uint num = 0u;
|
||||
int num2 = 12;
|
||||
for (int i = 0; i < 13; i++)
|
||||
{
|
||||
if (i == 5)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
InventoryItem* inventorySlot = container->GetInventorySlot(i);
|
||||
if (inventorySlot == null || inventorySlot->ItemId == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Item? rowOrDefault = _itemSheet.GetRowOrDefault(inventorySlot->ItemId);
|
||||
if (!rowOrDefault.HasValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (rowOrDefault.Value.ItemUICategory.RowId == 105)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
num2--;
|
||||
}
|
||||
num2--;
|
||||
continue;
|
||||
}
|
||||
if (i == 0 && !CanHaveOffhand.Contains(rowOrDefault.Value.ItemUICategory.RowId))
|
||||
{
|
||||
num += rowOrDefault.Value.LevelItem.RowId;
|
||||
i++;
|
||||
}
|
||||
num += rowOrDefault.Value.LevelItem.RowId;
|
||||
}
|
||||
return (short)(num / num2);
|
||||
}
|
||||
}
|
6
LLib/LLib.Gear/StatInfo.cs
Normal file
6
LLib/LLib.Gear/StatInfo.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace LLib.Gear;
|
||||
|
||||
public sealed record StatInfo(short EquipmentValue, short MateriaValue, bool Overcapped)
|
||||
{
|
||||
public short TotalValue => (short)(EquipmentValue + MateriaValue);
|
||||
}
|
14
LLib/LLib.ImGui/IPersistableWindowConfig.cs
Normal file
14
LLib/LLib.ImGui/IPersistableWindowConfig.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace LLib.ImGui;
|
||||
|
||||
public interface IPersistableWindowConfig
|
||||
{
|
||||
WindowConfig? WindowConfig { get; }
|
||||
|
||||
void SaveWindowConfig();
|
||||
}
|
||||
public interface IPersistableWindowConfig<out T> : IPersistableWindowConfig where T : WindowConfig
|
||||
{
|
||||
new T? WindowConfig { get; }
|
||||
|
||||
WindowConfig? IPersistableWindowConfig.WindowConfig => WindowConfig;
|
||||
}
|
188
LLib/LLib.ImGui/LWindow.cs
Normal file
188
LLib/LLib.ImGui/LWindow.cs
Normal file
|
@ -0,0 +1,188 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Windowing;
|
||||
|
||||
namespace LLib.ImGui;
|
||||
|
||||
public abstract class LWindow : Window
|
||||
{
|
||||
private bool _initializedConfig;
|
||||
|
||||
private bool _wasCollapsedLastFrame;
|
||||
|
||||
protected bool ClickedHeaderLastFrame { get; private set; }
|
||||
|
||||
protected bool ClickedHeaderCurrentFrame { get; private set; }
|
||||
|
||||
protected bool UncollapseNextFrame { get; set; }
|
||||
|
||||
public bool IsOpenAndUncollapsed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (base.IsOpen)
|
||||
{
|
||||
return !_wasCollapsedLastFrame;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.IsOpen = value;
|
||||
UncollapseNextFrame = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsPinned
|
||||
{
|
||||
get
|
||||
{
|
||||
return InternalIsPinned(this);
|
||||
}
|
||||
set
|
||||
{
|
||||
InternalIsPinned(this) = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsClickthrough
|
||||
{
|
||||
get
|
||||
{
|
||||
return InternalIsClickthrough(this);
|
||||
}
|
||||
set
|
||||
{
|
||||
InternalIsClickthrough(this) = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected int? Alpha
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int?)(100000f * InternalAlpha(this));
|
||||
}
|
||||
set
|
||||
{
|
||||
InternalAlpha(this) = (float?)value / 100000f;
|
||||
}
|
||||
}
|
||||
|
||||
protected LWindow(string windowName, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false)
|
||||
: base(windowName, flags, forceMainWindow)
|
||||
{
|
||||
}
|
||||
|
||||
private void LoadWindowConfig()
|
||||
{
|
||||
if (!(this is IPersistableWindowConfig persistableWindowConfig))
|
||||
{
|
||||
return;
|
||||
}
|
||||
WindowConfig windowConfig = persistableWindowConfig.WindowConfig;
|
||||
if (windowConfig != null)
|
||||
{
|
||||
if (base.AllowPinning)
|
||||
{
|
||||
IsPinned = windowConfig.IsPinned;
|
||||
}
|
||||
if (base.AllowClickthrough)
|
||||
{
|
||||
IsClickthrough = windowConfig.IsClickthrough;
|
||||
}
|
||||
Alpha = windowConfig.Alpha;
|
||||
}
|
||||
_initializedConfig = true;
|
||||
}
|
||||
|
||||
private void UpdateWindowConfig()
|
||||
{
|
||||
if (!(this is IPersistableWindowConfig persistableWindowConfig) || Dalamud.Bindings.ImGui.ImGui.IsAnyMouseDown())
|
||||
{
|
||||
return;
|
||||
}
|
||||
WindowConfig windowConfig = persistableWindowConfig.WindowConfig;
|
||||
if (windowConfig != null)
|
||||
{
|
||||
bool flag = false;
|
||||
if (base.AllowPinning && windowConfig.IsPinned != IsPinned)
|
||||
{
|
||||
windowConfig.IsPinned = IsPinned;
|
||||
flag = true;
|
||||
}
|
||||
if (base.AllowClickthrough && windowConfig.IsClickthrough != IsClickthrough)
|
||||
{
|
||||
windowConfig.IsClickthrough = IsClickthrough;
|
||||
flag = true;
|
||||
}
|
||||
if (windowConfig.Alpha != Alpha)
|
||||
{
|
||||
windowConfig.Alpha = Alpha;
|
||||
flag = true;
|
||||
}
|
||||
if (flag)
|
||||
{
|
||||
persistableWindowConfig.SaveWindowConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleOrUncollapse()
|
||||
{
|
||||
IsOpenAndUncollapsed = !IsOpenAndUncollapsed;
|
||||
}
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
UncollapseNextFrame = true;
|
||||
base.OnOpen();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
_wasCollapsedLastFrame = true;
|
||||
}
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
if (!_initializedConfig)
|
||||
{
|
||||
LoadWindowConfig();
|
||||
}
|
||||
if (UncollapseNextFrame)
|
||||
{
|
||||
Dalamud.Bindings.ImGui.ImGui.SetNextWindowCollapsed(collapsed: false);
|
||||
UncollapseNextFrame = false;
|
||||
}
|
||||
base.PreDraw();
|
||||
ClickedHeaderLastFrame = ClickedHeaderCurrentFrame;
|
||||
ClickedHeaderCurrentFrame = false;
|
||||
}
|
||||
|
||||
public sealed override void Draw()
|
||||
{
|
||||
_wasCollapsedLastFrame = false;
|
||||
DrawContent();
|
||||
}
|
||||
|
||||
public abstract void DrawContent();
|
||||
|
||||
public override void PostDraw()
|
||||
{
|
||||
base.PostDraw();
|
||||
if (_initializedConfig)
|
||||
{
|
||||
UpdateWindowConfig();
|
||||
}
|
||||
}
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "internalIsPinned")]
|
||||
private static extern ref bool InternalIsPinned(Window @this);
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "internalIsClickthrough")]
|
||||
private static extern ref bool InternalIsClickthrough(Window @this);
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "internalAlpha")]
|
||||
private static extern ref float? InternalAlpha(Window @this);
|
||||
}
|
10
LLib/LLib.ImGui/WindowConfig.cs
Normal file
10
LLib/LLib.ImGui/WindowConfig.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace LLib.ImGui;
|
||||
|
||||
public class WindowConfig
|
||||
{
|
||||
public bool IsPinned { get; set; }
|
||||
|
||||
public bool IsClickthrough { get; set; }
|
||||
|
||||
public int? Alpha { get; set; }
|
||||
}
|
14
LLib/LLib.Shop.Model/ItemForSale.cs
Normal file
14
LLib/LLib.Shop.Model/ItemForSale.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace LLib.Shop.Model;
|
||||
|
||||
public sealed class ItemForSale
|
||||
{
|
||||
public required int Position { get; init; }
|
||||
|
||||
public required uint ItemId { get; init; }
|
||||
|
||||
public required string? ItemName { get; init; }
|
||||
|
||||
public required uint Price { get; init; }
|
||||
|
||||
public required uint OwnedItems { get; init; }
|
||||
}
|
24
LLib/LLib.Shop.Model/PurchaseState.cs
Normal file
24
LLib/LLib.Shop.Model/PurchaseState.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
|
||||
namespace LLib.Shop.Model;
|
||||
|
||||
public sealed class PurchaseState
|
||||
{
|
||||
public int DesiredItems { get; }
|
||||
|
||||
public int OwnedItems { get; set; }
|
||||
|
||||
public int ItemsLeftToBuy => Math.Max(0, DesiredItems - OwnedItems);
|
||||
|
||||
public bool IsComplete => ItemsLeftToBuy == 0;
|
||||
|
||||
public bool IsAwaitingYesNo { get; set; }
|
||||
|
||||
public DateTime NextStep { get; set; } = DateTime.MinValue;
|
||||
|
||||
public PurchaseState(int desiredItems, int ownedItems)
|
||||
{
|
||||
DesiredItems = desiredItems;
|
||||
OwnedItems = ownedItems;
|
||||
}
|
||||
}
|
23
LLib/LLib.Shop/IShopWindow.cs
Normal file
23
LLib/LLib.Shop/IShopWindow.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace LLib.Shop;
|
||||
|
||||
public interface IShopWindow
|
||||
{
|
||||
bool IsEnabled { get; }
|
||||
|
||||
bool IsOpen { get; set; }
|
||||
|
||||
Vector2? Position { get; set; }
|
||||
|
||||
int GetCurrencyCount();
|
||||
|
||||
unsafe void UpdateShopStock(AtkUnitBase* addon);
|
||||
|
||||
unsafe void TriggerPurchase(AtkUnitBase* addonShop, int buyNow);
|
||||
|
||||
void SaveExternalPluginState();
|
||||
|
||||
void RestoreExternalPluginState();
|
||||
}
|
275
LLib/LLib.Shop/RegularShopBase.cs
Normal file
275
LLib/LLib.Shop/RegularShopBase.cs
Normal file
|
@ -0,0 +1,275 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameUI;
|
||||
using LLib.Shop.Model;
|
||||
|
||||
namespace LLib.Shop;
|
||||
|
||||
public class RegularShopBase
|
||||
{
|
||||
private readonly IShopWindow _parentWindow;
|
||||
|
||||
private readonly string _addonName;
|
||||
|
||||
private readonly IPluginLog _pluginLog;
|
||||
|
||||
private readonly IGameGui _gameGui;
|
||||
|
||||
private readonly IAddonLifecycle _addonLifecycle;
|
||||
|
||||
public ItemForSale? ItemForSale { get; set; }
|
||||
|
||||
public PurchaseState? PurchaseState { get; private set; }
|
||||
|
||||
public bool AutoBuyEnabled => PurchaseState != null;
|
||||
|
||||
public bool IsAwaitingYesNo
|
||||
{
|
||||
get
|
||||
{
|
||||
return PurchaseState?.IsAwaitingYesNo ?? false;
|
||||
}
|
||||
set
|
||||
{
|
||||
PurchaseState.IsAwaitingYesNo = value;
|
||||
}
|
||||
}
|
||||
|
||||
public RegularShopBase(IShopWindow parentWindow, string addonName, IPluginLog pluginLog, IGameGui gameGui, IAddonLifecycle addonLifecycle)
|
||||
{
|
||||
_parentWindow = parentWindow;
|
||||
_addonName = addonName;
|
||||
_pluginLog = pluginLog;
|
||||
_gameGui = gameGui;
|
||||
_addonLifecycle = addonLifecycle;
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, _addonName, ShopPostSetup);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PreFinalize, _addonName, ShopPreFinalize);
|
||||
_addonLifecycle.RegisterListener(AddonEvent.PostUpdate, _addonName, ShopPostUpdate);
|
||||
}
|
||||
|
||||
private unsafe void ShopPostSetup(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
if (!_parentWindow.IsEnabled)
|
||||
{
|
||||
ItemForSale = null;
|
||||
_parentWindow.IsOpen = false;
|
||||
return;
|
||||
}
|
||||
_parentWindow.UpdateShopStock((AtkUnitBase*)args.Addon.Address);
|
||||
PostUpdateShopStock();
|
||||
if (ItemForSale != null)
|
||||
{
|
||||
_parentWindow.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShopPreFinalize(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
PurchaseState = null;
|
||||
_parentWindow.RestoreExternalPluginState();
|
||||
_parentWindow.IsOpen = false;
|
||||
}
|
||||
|
||||
private unsafe void ShopPostUpdate(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
if (!_parentWindow.IsEnabled)
|
||||
{
|
||||
ItemForSale = null;
|
||||
_parentWindow.IsOpen = false;
|
||||
return;
|
||||
}
|
||||
_parentWindow.UpdateShopStock((AtkUnitBase*)args.Addon.Address);
|
||||
PostUpdateShopStock();
|
||||
if (ItemForSale != null)
|
||||
{
|
||||
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
||||
short num = 0;
|
||||
short num2 = 0;
|
||||
address->GetPosition(&num, &num2);
|
||||
short num3 = 0;
|
||||
short num4 = 0;
|
||||
address->GetSize(&num3, &num4, scaled: true);
|
||||
num += num3;
|
||||
Vector2? position = _parentWindow.Position;
|
||||
if (position.HasValue)
|
||||
{
|
||||
Vector2 valueOrDefault = position.GetValueOrDefault();
|
||||
if ((short)valueOrDefault.X != num || (short)valueOrDefault.Y != num2)
|
||||
{
|
||||
_parentWindow.Position = new Vector2(num, num2);
|
||||
}
|
||||
}
|
||||
_parentWindow.IsOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_parentWindow.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PostUpdateShopStock()
|
||||
{
|
||||
if (ItemForSale != null && PurchaseState != null)
|
||||
{
|
||||
int ownedItems = (int)ItemForSale.OwnedItems;
|
||||
if (PurchaseState.OwnedItems != ownedItems)
|
||||
{
|
||||
PurchaseState.OwnedItems = ownedItems;
|
||||
PurchaseState.NextStep = DateTime.Now.AddSeconds(0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe int GetItemCount(uint itemId)
|
||||
{
|
||||
return InventoryManager.Instance()->GetInventoryItemCount(itemId, isHq: false, checkEquipped: false, checkArmory: false, 0);
|
||||
}
|
||||
|
||||
public int GetMaxItemsToPurchase()
|
||||
{
|
||||
if (ItemForSale == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (int)(_parentWindow.GetCurrencyCount() / ItemForSale.Price);
|
||||
}
|
||||
|
||||
public void CancelAutoPurchase()
|
||||
{
|
||||
PurchaseState = null;
|
||||
_parentWindow.RestoreExternalPluginState();
|
||||
}
|
||||
|
||||
public void StartAutoPurchase(int toPurchase)
|
||||
{
|
||||
PurchaseState = new PurchaseState((int)ItemForSale.OwnedItems + toPurchase, (int)ItemForSale.OwnedItems);
|
||||
_parentWindow.SaveExternalPluginState();
|
||||
}
|
||||
|
||||
public unsafe void HandleNextPurchaseStep()
|
||||
{
|
||||
if (ItemForSale == null || PurchaseState == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int num = DetermineMaxStackSize(ItemForSale.ItemId);
|
||||
if (num == 0 && !HasFreeInventorySlot())
|
||||
{
|
||||
_pluginLog.Warning("No free inventory slots, can't buy more " + ItemForSale.ItemName);
|
||||
PurchaseState = null;
|
||||
_parentWindow.RestoreExternalPluginState();
|
||||
}
|
||||
else if (!PurchaseState.IsComplete)
|
||||
{
|
||||
if (PurchaseState.NextStep <= DateTime.Now && _gameGui.TryGetAddonByName<AtkUnitBase>(_addonName, out var addonPtr))
|
||||
{
|
||||
int num2 = Math.Min(PurchaseState.ItemsLeftToBuy, num);
|
||||
_pluginLog.Information($"Buying {num2}x {ItemForSale.ItemName}");
|
||||
_parentWindow.TriggerPurchase(addonPtr, num2);
|
||||
PurchaseState.NextStep = DateTime.MaxValue;
|
||||
PurchaseState.IsAwaitingYesNo = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pluginLog.Information($"Stopping item purchase (desired = {PurchaseState.DesiredItems}, owned = {PurchaseState.OwnedItems})");
|
||||
PurchaseState = null;
|
||||
_parentWindow.RestoreExternalPluginState();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, _addonName, ShopPostSetup);
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PreFinalize, _addonName, ShopPreFinalize);
|
||||
_addonLifecycle.UnregisterListener(AddonEvent.PostUpdate, _addonName, ShopPostUpdate);
|
||||
}
|
||||
|
||||
public bool HasFreeInventorySlot()
|
||||
{
|
||||
return CountFreeInventorySlots() > 0;
|
||||
}
|
||||
|
||||
public unsafe int CountFreeInventorySlots()
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int num = 0;
|
||||
for (InventoryType inventoryType = InventoryType.Inventory1; inventoryType <= InventoryType.Inventory4; inventoryType++)
|
||||
{
|
||||
InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType);
|
||||
for (int i = 0; i < inventoryContainer->Size; i++)
|
||||
{
|
||||
InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i);
|
||||
if (inventorySlot == null || inventorySlot->ItemId == 0)
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
private unsafe int DetermineMaxStackSize(uint itemId)
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int num = 0;
|
||||
for (InventoryType inventoryType = InventoryType.Inventory1; inventoryType <= InventoryType.Inventory4; inventoryType++)
|
||||
{
|
||||
InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType);
|
||||
for (int i = 0; i < inventoryContainer->Size; i++)
|
||||
{
|
||||
InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i);
|
||||
if (inventorySlot == null || inventorySlot->ItemId == 0)
|
||||
{
|
||||
return 99;
|
||||
}
|
||||
if (inventorySlot->ItemId == itemId)
|
||||
{
|
||||
num += 999 - inventorySlot->Quantity;
|
||||
if (num >= 99)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.Min(99, num);
|
||||
}
|
||||
|
||||
public unsafe int CountInventorySlotsWithCondition(uint itemId, Predicate<int> predicate)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(predicate, "predicate");
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int num = 0;
|
||||
for (InventoryType inventoryType = InventoryType.Inventory1; inventoryType <= InventoryType.Inventory4; inventoryType++)
|
||||
{
|
||||
InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType);
|
||||
for (int i = 0; i < inventoryContainer->Size; i++)
|
||||
{
|
||||
InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i);
|
||||
if (inventorySlot != null && inventorySlot->ItemId != 0 && inventorySlot->ItemId == itemId && predicate(inventorySlot->Quantity))
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
}
|
35
LLib/LLib.csproj
Normal file
35
LLib/LLib.csproj
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>LLib</AssemblyName>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<TargetFramework>netcoreapp9.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup />
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Dalamud.Bindings.ImGui">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.Bindings.ImGui.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="InteropGenerator.Runtime">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\InteropGenerator.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
100
LLib/LLib/DalamudReflector.cs
Normal file
100
LLib/LLib/DalamudReflector.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace LLib;
|
||||
|
||||
public sealed class DalamudReflector : IDisposable
|
||||
{
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
private readonly IFramework _framework;
|
||||
|
||||
private readonly IPluginLog _pluginLog;
|
||||
|
||||
private readonly Dictionary<string, IDalamudPlugin> _pluginCache = new Dictionary<string, IDalamudPlugin>();
|
||||
|
||||
private bool _pluginsChanged;
|
||||
|
||||
public DalamudReflector(IDalamudPluginInterface pluginInterface, IFramework framework, IPluginLog pluginLog)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
_framework = framework;
|
||||
_pluginLog = pluginLog;
|
||||
object pluginManager = GetPluginManager();
|
||||
pluginManager.GetType().GetEvent("OnInstalledPluginsChanged").AddEventHandler(pluginManager, new Action(OnInstalledPluginsChanged));
|
||||
_framework.Update += FrameworkUpdate;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_framework.Update -= FrameworkUpdate;
|
||||
object pluginManager = GetPluginManager();
|
||||
pluginManager.GetType().GetEvent("OnInstalledPluginsChanged").RemoveEventHandler(pluginManager, new Action(OnInstalledPluginsChanged));
|
||||
}
|
||||
|
||||
private void FrameworkUpdate(IFramework framework)
|
||||
{
|
||||
if (_pluginsChanged)
|
||||
{
|
||||
_pluginsChanged = false;
|
||||
_pluginCache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private object GetPluginManager()
|
||||
{
|
||||
return _pluginInterface.GetType().Assembly.GetType("Dalamud.Service`1", throwOnError: true).MakeGenericType(_pluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", throwOnError: true)).GetMethod("Get")
|
||||
.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null);
|
||||
}
|
||||
|
||||
public bool TryGetDalamudPlugin(string internalName, [MaybeNullWhen(false)] out IDalamudPlugin instance, bool suppressErrors = false, bool ignoreCache = false)
|
||||
{
|
||||
if (!ignoreCache && _pluginCache.TryGetValue(internalName, out instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
try
|
||||
{
|
||||
object pluginManager = GetPluginManager();
|
||||
foreach (object item in (IList)pluginManager.GetType().GetProperty("InstalledPlugins").GetValue(pluginManager))
|
||||
{
|
||||
if ((string)item.GetType().GetProperty("Name").GetValue(item) == internalName)
|
||||
{
|
||||
IDalamudPlugin dalamudPlugin = (IDalamudPlugin)((item.GetType().Name == "LocalDevPlugin") ? item.GetType().BaseType : item.GetType()).GetField("instance", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(item);
|
||||
if (dalamudPlugin != null)
|
||||
{
|
||||
instance = dalamudPlugin;
|
||||
_pluginCache[internalName] = dalamudPlugin;
|
||||
return true;
|
||||
}
|
||||
if (!suppressErrors)
|
||||
{
|
||||
_pluginLog.Warning("[DalamudReflector] Found requested plugin " + internalName + " but it was null");
|
||||
}
|
||||
}
|
||||
}
|
||||
instance = null;
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!suppressErrors)
|
||||
{
|
||||
_pluginLog.Error(ex, "Can't find " + internalName + " plugin: " + ex.Message);
|
||||
}
|
||||
instance = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstalledPluginsChanged()
|
||||
{
|
||||
_pluginLog.Verbose("Installed plugins changed event fired");
|
||||
_pluginsChanged = true;
|
||||
}
|
||||
}
|
104
LLib/LLib/DataManagerExtensions.cs
Normal file
104
LLib/LLib/DataManagerExtensions.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace LLib;
|
||||
|
||||
public static class DataManagerExtensions
|
||||
{
|
||||
public static ReadOnlySeString? GetSeString<T>(this IDataManager dataManager, string key) where T : struct, IQuestDialogueText, IExcelRow<T>
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dataManager, "dataManager");
|
||||
return dataManager.GetExcelSheet<T>().Cast<T?>().SingleOrDefault((T? x) => x.Value.Key == key)?.Value;
|
||||
}
|
||||
|
||||
public static string? GetString<T>(this IDataManager dataManager, string key, IPluginLog? pluginLog) where T : struct, IQuestDialogueText, IExcelRow<T>
|
||||
{
|
||||
string text = dataManager.GetSeString<T>(key)?.WithCertainMacroCodeReplacements();
|
||||
pluginLog?.Verbose($"{typeof(T).Name}.{key} => {text}");
|
||||
return text;
|
||||
}
|
||||
|
||||
public static Regex? GetRegex<T>(this IDataManager dataManager, string key, IPluginLog? pluginLog) where T : struct, IQuestDialogueText, IExcelRow<T>
|
||||
{
|
||||
ReadOnlySeString? seString = dataManager.GetSeString<T>(key);
|
||||
if (!seString.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string text = string.Join("", seString.Select((ReadOnlySePayload payload) => (payload.Type == ReadOnlySePayloadType.Text) ? Regex.Escape(payload.ToString()) : "(.*)"));
|
||||
pluginLog?.Verbose($"{typeof(T).Name}.{key} => /{text}/");
|
||||
return new Regex(text);
|
||||
}
|
||||
|
||||
public static ReadOnlySeString? GetSeString<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper) where T : struct, IExcelRow<T>
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dataManager, "dataManager");
|
||||
ArgumentNullException.ThrowIfNull(mapper, "mapper");
|
||||
T? rowOrDefault = dataManager.GetExcelSheet<T>().GetRowOrDefault(rowId);
|
||||
if (!rowOrDefault.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return mapper(rowOrDefault.Value);
|
||||
}
|
||||
|
||||
public static string? GetString<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog = null) where T : struct, IExcelRow<T>
|
||||
{
|
||||
string text = dataManager.GetSeString(rowId, mapper)?.WithCertainMacroCodeReplacements();
|
||||
pluginLog?.Verbose($"{typeof(T).Name}.{rowId} => {text}");
|
||||
return text;
|
||||
}
|
||||
|
||||
public static Regex? GetRegex<T>(this IDataManager dataManager, uint rowId, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog = null) where T : struct, IExcelRow<T>
|
||||
{
|
||||
ReadOnlySeString? seString = dataManager.GetSeString(rowId, mapper);
|
||||
if (!seString.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Regex regex = seString.ToRegex();
|
||||
pluginLog?.Verbose($"{typeof(T).Name}.{rowId} => /{regex}/");
|
||||
return regex;
|
||||
}
|
||||
|
||||
public static Regex? GetRegex<T>(this T excelRow, Func<T, ReadOnlySeString?> mapper, IPluginLog? pluginLog) where T : struct, IExcelRow<T>
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(mapper, "mapper");
|
||||
ReadOnlySeString? text = mapper(excelRow);
|
||||
if (!text.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Regex regex = text.ToRegex();
|
||||
pluginLog?.Verbose($"{typeof(T).Name}.regex => /{regex}/");
|
||||
return regex;
|
||||
}
|
||||
|
||||
public static Regex ToRegex(this ReadOnlySeString? text)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(text, "text");
|
||||
return new Regex(string.Join("", text.Value.Select((ReadOnlySePayload payload) => (payload.Type == ReadOnlySePayloadType.Text) ? Regex.Escape(payload.ToString()) : "(.*)")));
|
||||
}
|
||||
|
||||
public static string WithCertainMacroCodeReplacements(this ReadOnlySeString text)
|
||||
{
|
||||
return string.Join("", text.Select((ReadOnlySePayload payload) => payload.Type switch
|
||||
{
|
||||
ReadOnlySePayloadType.Text => payload.ToString(),
|
||||
ReadOnlySePayloadType.Macro => payload.MacroCode switch
|
||||
{
|
||||
MacroCode.NewLine => "",
|
||||
MacroCode.NonBreakingSpace => " ",
|
||||
MacroCode.Hyphen => "-",
|
||||
MacroCode.SoftHyphen => "",
|
||||
_ => payload.ToString(),
|
||||
},
|
||||
_ => payload.ToString(),
|
||||
}));
|
||||
}
|
||||
}
|
10
LLib/LLib/IQuestDialogueText.cs
Normal file
10
LLib/LLib/IQuestDialogueText.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace LLib;
|
||||
|
||||
public interface IQuestDialogueText
|
||||
{
|
||||
ReadOnlySeString Key { get; }
|
||||
|
||||
ReadOnlySeString Value { get; }
|
||||
}
|
26
LLib/LLib/QuestDialogueText.cs
Normal file
26
LLib/LLib/QuestDialogueText.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using Lumina.Excel;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace LLib;
|
||||
|
||||
[Sheet("PleaseSpecifyTheSheetExplicitly")]
|
||||
public readonly struct QuestDialogueText : IQuestDialogueText, IExcelRow<QuestDialogueText>
|
||||
{
|
||||
public uint RowId => _003Crow_003EP;
|
||||
|
||||
public ReadOnlySeString Key => _003Cpage_003EP.ReadString(_003Coffset_003EP, _003Coffset_003EP);
|
||||
|
||||
public ReadOnlySeString Value => _003Cpage_003EP.ReadString(_003Coffset_003EP + 4, _003Coffset_003EP);
|
||||
|
||||
public QuestDialogueText(ExcelPage page, uint offset, uint row)
|
||||
{
|
||||
_003Cpage_003EP = page;
|
||||
_003Coffset_003EP = offset;
|
||||
_003Crow_003EP = row;
|
||||
}
|
||||
|
||||
static QuestDialogueText IExcelRow<QuestDialogueText>.Create(ExcelPage page, uint offset, uint row)
|
||||
{
|
||||
return new QuestDialogueText(page, offset, row);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue