mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add equipment swaps and writing to option.
This commit is contained in:
parent
33b4905ae2
commit
ab53f17a7e
9 changed files with 723 additions and 526 deletions
82
Penumbra.GameData/Data/ItemData.cs
Normal file
82
Penumbra.GameData/Data/ItemData.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Penumbra.GameData.Data;
|
||||||
|
|
||||||
|
public sealed class ItemData : DataSharer, IReadOnlyDictionary<FullEquipType, IReadOnlyList<Item>>
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<IReadOnlyList<Item>> _items;
|
||||||
|
|
||||||
|
private static IReadOnlyList<IReadOnlyList<Item>> CreateItems(DataManager dataManager, ClientLanguage language)
|
||||||
|
{
|
||||||
|
var tmp = Enum.GetValues<FullEquipType>().Select(t => new List<Item>(1024)).ToArray();
|
||||||
|
|
||||||
|
var itemSheet = dataManager.GetExcelSheet<Item>(language)!;
|
||||||
|
foreach (var item in itemSheet)
|
||||||
|
{
|
||||||
|
var type = item.ToEquipType();
|
||||||
|
if (type != FullEquipType.Unknown && item.Name.RawData.Length > 1)
|
||||||
|
tmp[(int)type].Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = new IReadOnlyList<Item>[tmp.Length];
|
||||||
|
ret[0] = Array.Empty<Item>();
|
||||||
|
for (var i = 1; i < tmp.Length; ++i)
|
||||||
|
ret[i] = tmp[i].OrderBy(item => item.Name.ToDalamudString().TextValue).ToArray();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemData(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language)
|
||||||
|
: base(pluginInterface, language, 1)
|
||||||
|
{
|
||||||
|
_items = TryCatchData("ItemList", () => CreateItems(dataManager, language));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DisposeInternal()
|
||||||
|
=> DisposeTag("ItemList");
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<FullEquipType, IReadOnlyList<Item>>> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (var i = 1; i < _items.Count; ++i)
|
||||||
|
yield return new KeyValuePair<FullEquipType, IReadOnlyList<Item>>((FullEquipType)i, _items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> GetEnumerator();
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
=> _items.Count - 1;
|
||||||
|
|
||||||
|
public bool ContainsKey(FullEquipType key)
|
||||||
|
=> (int)key < _items.Count && key != FullEquipType.Unknown;
|
||||||
|
|
||||||
|
public bool TryGetValue(FullEquipType key, out IReadOnlyList<Item> value)
|
||||||
|
{
|
||||||
|
if (ContainsKey(key))
|
||||||
|
{
|
||||||
|
value = _items[(int)key];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = _items[0];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<Item> this[FullEquipType key]
|
||||||
|
=> TryGetValue(key, out var ret) ? ret : throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
|
public IEnumerable<FullEquipType> Keys
|
||||||
|
=> Enum.GetValues<FullEquipType>().Skip(1);
|
||||||
|
|
||||||
|
public IEnumerable<IReadOnlyList<Item>> Values
|
||||||
|
=> _items.Skip(1);
|
||||||
|
}
|
||||||
358
Penumbra.GameData/Enums/FullEquipType.cs
Normal file
358
Penumbra.GameData/Enums/FullEquipType.cs
Normal file
|
|
@ -0,0 +1,358 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
public enum FullEquipType : byte
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
|
||||||
|
Head,
|
||||||
|
Body,
|
||||||
|
Hands,
|
||||||
|
Legs,
|
||||||
|
Feet,
|
||||||
|
|
||||||
|
Ears,
|
||||||
|
Neck,
|
||||||
|
Wrists,
|
||||||
|
Finger,
|
||||||
|
|
||||||
|
Fists, // PGL, MNK
|
||||||
|
Sword, // GLA, PLD Main
|
||||||
|
Axe, // MRD, WAR
|
||||||
|
Bow, // ARC, BRD
|
||||||
|
Lance, // LNC, DRG,
|
||||||
|
Staff, // THM, BLM, CNJ, WHM
|
||||||
|
Wand, // THM, BLM, CNJ, WHM Main
|
||||||
|
Book, // ACN, SMN, SCH
|
||||||
|
Daggers, // ROG, NIN
|
||||||
|
Broadsword, // DRK,
|
||||||
|
Gun, // MCH,
|
||||||
|
Orrery, // AST,
|
||||||
|
Katana, // SAM
|
||||||
|
Rapier, // RDM
|
||||||
|
Cane, // BLU
|
||||||
|
Gunblade, // GNB,
|
||||||
|
Glaives, // DNC,
|
||||||
|
Scythe, // RPR,
|
||||||
|
Nouliths, // SGE
|
||||||
|
Shield, // GLA, PLD, THM, BLM, CNJ, WHM Off
|
||||||
|
|
||||||
|
Saw, // CRP
|
||||||
|
CrossPeinHammer, // BSM
|
||||||
|
RaisingHammer, // ARM
|
||||||
|
LapidaryHammer, // GSM
|
||||||
|
Knife, // LTW
|
||||||
|
Needle, // WVR
|
||||||
|
Alembic, // ALC
|
||||||
|
Frypan, // CUL
|
||||||
|
Pickaxe, // MIN
|
||||||
|
Hatchet, // BTN
|
||||||
|
FishingRod, // FSH
|
||||||
|
|
||||||
|
ClawHammer, // CRP Off
|
||||||
|
File, // BSM Off
|
||||||
|
Pliers, // ARM Off
|
||||||
|
GrindingWheel, // GSM Off
|
||||||
|
Awl, // LTW Off
|
||||||
|
SpinningWheel, // WVR Off
|
||||||
|
Mortar, // ALC Off
|
||||||
|
CulinaryKnife, // CUL Off
|
||||||
|
Sledgehammer, // MIN Off
|
||||||
|
GardenScythe, // BTN Off
|
||||||
|
Gig, // FSH Off
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FullEquipTypeExtensions
|
||||||
|
{
|
||||||
|
public static FullEquipType ToEquipType(this Item item)
|
||||||
|
{
|
||||||
|
var slot = (EquipSlot)item.EquipSlotCategory.Row;
|
||||||
|
var weapon = (WeaponCategory)item.ItemUICategory.Row;
|
||||||
|
return slot.ToEquipType(weapon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWeapon(this FullEquipType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
FullEquipType.Fists => true,
|
||||||
|
FullEquipType.Sword => true,
|
||||||
|
FullEquipType.Axe => true,
|
||||||
|
FullEquipType.Bow => true,
|
||||||
|
FullEquipType.Lance => true,
|
||||||
|
FullEquipType.Staff => true,
|
||||||
|
FullEquipType.Wand => true,
|
||||||
|
FullEquipType.Book => true,
|
||||||
|
FullEquipType.Daggers => true,
|
||||||
|
FullEquipType.Broadsword => true,
|
||||||
|
FullEquipType.Gun => true,
|
||||||
|
FullEquipType.Orrery => true,
|
||||||
|
FullEquipType.Katana => true,
|
||||||
|
FullEquipType.Rapier => true,
|
||||||
|
FullEquipType.Cane => true,
|
||||||
|
FullEquipType.Gunblade => true,
|
||||||
|
FullEquipType.Glaives => true,
|
||||||
|
FullEquipType.Scythe => true,
|
||||||
|
FullEquipType.Nouliths => true,
|
||||||
|
FullEquipType.Shield => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsTool(this FullEquipType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
FullEquipType.Saw => true,
|
||||||
|
FullEquipType.CrossPeinHammer => true,
|
||||||
|
FullEquipType.RaisingHammer => true,
|
||||||
|
FullEquipType.LapidaryHammer => true,
|
||||||
|
FullEquipType.Knife => true,
|
||||||
|
FullEquipType.Needle => true,
|
||||||
|
FullEquipType.Alembic => true,
|
||||||
|
FullEquipType.Frypan => true,
|
||||||
|
FullEquipType.Pickaxe => true,
|
||||||
|
FullEquipType.Hatchet => true,
|
||||||
|
FullEquipType.FishingRod => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsEquipment(this FullEquipType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
FullEquipType.Head => true,
|
||||||
|
FullEquipType.Body => true,
|
||||||
|
FullEquipType.Hands => true,
|
||||||
|
FullEquipType.Legs => true,
|
||||||
|
FullEquipType.Feet => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsAccessory(this FullEquipType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
FullEquipType.Ears => true,
|
||||||
|
FullEquipType.Neck => true,
|
||||||
|
FullEquipType.Wrists => true,
|
||||||
|
FullEquipType.Finger => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string ToName(this FullEquipType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
FullEquipType.Head => EquipSlot.Head.ToName(),
|
||||||
|
FullEquipType.Body => EquipSlot.Body.ToName(),
|
||||||
|
FullEquipType.Hands => EquipSlot.Hands.ToName(),
|
||||||
|
FullEquipType.Legs => EquipSlot.Legs.ToName(),
|
||||||
|
FullEquipType.Feet => EquipSlot.Feet.ToName(),
|
||||||
|
FullEquipType.Ears => EquipSlot.Ears.ToName(),
|
||||||
|
FullEquipType.Neck => EquipSlot.Neck.ToName(),
|
||||||
|
FullEquipType.Wrists => EquipSlot.Wrists.ToName(),
|
||||||
|
FullEquipType.Finger => "Ring",
|
||||||
|
FullEquipType.Fists => "Fist Weapon",
|
||||||
|
FullEquipType.Sword => "Sword",
|
||||||
|
FullEquipType.Axe => "Axe",
|
||||||
|
FullEquipType.Bow => "Bow",
|
||||||
|
FullEquipType.Lance => "Lance",
|
||||||
|
FullEquipType.Staff => "Staff",
|
||||||
|
FullEquipType.Wand => "Mace",
|
||||||
|
FullEquipType.Book => "Book",
|
||||||
|
FullEquipType.Daggers => "Dagger",
|
||||||
|
FullEquipType.Broadsword => "Broadsword",
|
||||||
|
FullEquipType.Gun => "Gun",
|
||||||
|
FullEquipType.Orrery => "Orrery",
|
||||||
|
FullEquipType.Katana => "Katana",
|
||||||
|
FullEquipType.Rapier => "Rapier",
|
||||||
|
FullEquipType.Cane => "Cane",
|
||||||
|
FullEquipType.Gunblade => "Gunblade",
|
||||||
|
FullEquipType.Glaives => "Glaive",
|
||||||
|
FullEquipType.Scythe => "Scythe",
|
||||||
|
FullEquipType.Nouliths => "Nouliths",
|
||||||
|
FullEquipType.Shield => "Shield",
|
||||||
|
FullEquipType.Saw => "Saw (Carpenter)",
|
||||||
|
FullEquipType.CrossPeinHammer => "Hammer (Blacksmith)",
|
||||||
|
FullEquipType.RaisingHammer => "Hammer (Armorsmith)",
|
||||||
|
FullEquipType.LapidaryHammer => "Hammer (Goldsmith)",
|
||||||
|
FullEquipType.Knife => "Knife (Leatherworker)",
|
||||||
|
FullEquipType.Needle => "Needle (Weaver)",
|
||||||
|
FullEquipType.Alembic => "Alembic (Alchemist)",
|
||||||
|
FullEquipType.Frypan => "Frypan (Culinarian)",
|
||||||
|
FullEquipType.Pickaxe => "Pickaxe (Miner)",
|
||||||
|
FullEquipType.Hatchet => "Hatchet (Botanist)",
|
||||||
|
FullEquipType.FishingRod => "Fishing Rod",
|
||||||
|
FullEquipType.ClawHammer => "Clawhammer (Carpenter)",
|
||||||
|
FullEquipType.File => "File (Blacksmith)",
|
||||||
|
FullEquipType.Pliers => "Pliers (Armorsmith)",
|
||||||
|
FullEquipType.GrindingWheel => "Grinding Wheel (Goldsmith)",
|
||||||
|
FullEquipType.Awl => "Awl (Leatherworker)",
|
||||||
|
FullEquipType.SpinningWheel => "Spinning Wheel (Weaver)",
|
||||||
|
FullEquipType.Mortar => "Mortar (Alchemist)",
|
||||||
|
FullEquipType.CulinaryKnife => "Knife (Culinarian)",
|
||||||
|
FullEquipType.Sledgehammer => "Sledgehammer (Miner)",
|
||||||
|
FullEquipType.GardenScythe => "Garden Scythe (Botanist)",
|
||||||
|
FullEquipType.Gig => "Gig (Fisher)",
|
||||||
|
_ => "Unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static EquipSlot ToSlot(this FullEquipType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
FullEquipType.Head => EquipSlot.Head,
|
||||||
|
FullEquipType.Body => EquipSlot.Body,
|
||||||
|
FullEquipType.Hands => EquipSlot.Hands,
|
||||||
|
FullEquipType.Legs => EquipSlot.Legs,
|
||||||
|
FullEquipType.Feet => EquipSlot.Feet,
|
||||||
|
FullEquipType.Ears => EquipSlot.Ears,
|
||||||
|
FullEquipType.Neck => EquipSlot.Neck,
|
||||||
|
FullEquipType.Wrists => EquipSlot.Wrists,
|
||||||
|
FullEquipType.Finger => EquipSlot.RFinger,
|
||||||
|
FullEquipType.Fists => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Sword => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Axe => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Bow => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Lance => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Staff => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Wand => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Book => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Daggers => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Broadsword => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Gun => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Orrery => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Katana => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Rapier => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Cane => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Gunblade => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Glaives => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Scythe => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Nouliths => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Shield => EquipSlot.OffHand,
|
||||||
|
FullEquipType.Saw => EquipSlot.MainHand,
|
||||||
|
FullEquipType.CrossPeinHammer => EquipSlot.MainHand,
|
||||||
|
FullEquipType.RaisingHammer => EquipSlot.MainHand,
|
||||||
|
FullEquipType.LapidaryHammer => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Knife => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Needle => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Alembic => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Frypan => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Pickaxe => EquipSlot.MainHand,
|
||||||
|
FullEquipType.Hatchet => EquipSlot.MainHand,
|
||||||
|
FullEquipType.FishingRod => EquipSlot.MainHand,
|
||||||
|
FullEquipType.ClawHammer => EquipSlot.OffHand,
|
||||||
|
FullEquipType.File => EquipSlot.OffHand,
|
||||||
|
FullEquipType.Pliers => EquipSlot.OffHand,
|
||||||
|
FullEquipType.GrindingWheel => EquipSlot.OffHand,
|
||||||
|
FullEquipType.Awl => EquipSlot.OffHand,
|
||||||
|
FullEquipType.SpinningWheel => EquipSlot.OffHand,
|
||||||
|
FullEquipType.Mortar => EquipSlot.OffHand,
|
||||||
|
FullEquipType.CulinaryKnife => EquipSlot.OffHand,
|
||||||
|
FullEquipType.Sledgehammer => EquipSlot.OffHand,
|
||||||
|
FullEquipType.GardenScythe => EquipSlot.OffHand,
|
||||||
|
FullEquipType.Gig => EquipSlot.OffHand,
|
||||||
|
_ => EquipSlot.Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static FullEquipType ToEquipType(this EquipSlot slot, WeaponCategory category = WeaponCategory.Unknown)
|
||||||
|
=> slot switch
|
||||||
|
{
|
||||||
|
EquipSlot.Head => FullEquipType.Head,
|
||||||
|
EquipSlot.Body => FullEquipType.Body,
|
||||||
|
EquipSlot.Hands => FullEquipType.Hands,
|
||||||
|
EquipSlot.Legs => FullEquipType.Legs,
|
||||||
|
EquipSlot.Feet => FullEquipType.Feet,
|
||||||
|
EquipSlot.Ears => FullEquipType.Ears,
|
||||||
|
EquipSlot.Neck => FullEquipType.Neck,
|
||||||
|
EquipSlot.Wrists => FullEquipType.Wrists,
|
||||||
|
EquipSlot.RFinger => FullEquipType.Finger,
|
||||||
|
EquipSlot.LFinger => FullEquipType.Finger,
|
||||||
|
EquipSlot.HeadBody => FullEquipType.Body,
|
||||||
|
EquipSlot.BodyHandsLegsFeet => FullEquipType.Body,
|
||||||
|
EquipSlot.LegsFeet => FullEquipType.Legs,
|
||||||
|
EquipSlot.FullBody => FullEquipType.Body,
|
||||||
|
EquipSlot.BodyHands => FullEquipType.Body,
|
||||||
|
EquipSlot.BodyLegsFeet => FullEquipType.Body,
|
||||||
|
EquipSlot.ChestHands => FullEquipType.Body,
|
||||||
|
EquipSlot.MainHand => category.ToEquipType(),
|
||||||
|
EquipSlot.OffHand => category.ToEquipType(),
|
||||||
|
EquipSlot.BothHand => category.ToEquipType(),
|
||||||
|
_ => FullEquipType.Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static FullEquipType ToEquipType(this WeaponCategory category)
|
||||||
|
=> category switch
|
||||||
|
{
|
||||||
|
WeaponCategory.Pugilist => FullEquipType.Fists,
|
||||||
|
WeaponCategory.Gladiator => FullEquipType.Sword,
|
||||||
|
WeaponCategory.Marauder => FullEquipType.Axe,
|
||||||
|
WeaponCategory.Archer => FullEquipType.Bow,
|
||||||
|
WeaponCategory.Lancer => FullEquipType.Lance,
|
||||||
|
WeaponCategory.Thaumaturge1 => FullEquipType.Wand,
|
||||||
|
WeaponCategory.Thaumaturge2 => FullEquipType.Staff,
|
||||||
|
WeaponCategory.Conjurer1 => FullEquipType.Wand,
|
||||||
|
WeaponCategory.Conjurer2 => FullEquipType.Staff,
|
||||||
|
WeaponCategory.Arcanist => FullEquipType.Book,
|
||||||
|
WeaponCategory.Shield => FullEquipType.Shield,
|
||||||
|
WeaponCategory.CarpenterMain => FullEquipType.Saw,
|
||||||
|
WeaponCategory.CarpenterOff => FullEquipType.ClawHammer,
|
||||||
|
WeaponCategory.BlacksmithMain => FullEquipType.CrossPeinHammer,
|
||||||
|
WeaponCategory.BlacksmithOff => FullEquipType.File,
|
||||||
|
WeaponCategory.ArmorerMain => FullEquipType.RaisingHammer,
|
||||||
|
WeaponCategory.ArmorerOff => FullEquipType.Pliers,
|
||||||
|
WeaponCategory.GoldsmithMain => FullEquipType.LapidaryHammer,
|
||||||
|
WeaponCategory.GoldsmithOff => FullEquipType.GrindingWheel,
|
||||||
|
WeaponCategory.LeatherworkerMain => FullEquipType.Knife,
|
||||||
|
WeaponCategory.LeatherworkerOff => FullEquipType.Awl,
|
||||||
|
WeaponCategory.WeaverMain => FullEquipType.Needle,
|
||||||
|
WeaponCategory.WeaverOff => FullEquipType.SpinningWheel,
|
||||||
|
WeaponCategory.AlchemistMain => FullEquipType.Alembic,
|
||||||
|
WeaponCategory.AlchemistOff => FullEquipType.Mortar,
|
||||||
|
WeaponCategory.CulinarianMain => FullEquipType.Frypan,
|
||||||
|
WeaponCategory.CulinarianOff => FullEquipType.CulinaryKnife,
|
||||||
|
WeaponCategory.MinerMain => FullEquipType.Pickaxe,
|
||||||
|
WeaponCategory.MinerOff => FullEquipType.Sledgehammer,
|
||||||
|
WeaponCategory.BotanistMain => FullEquipType.Hatchet,
|
||||||
|
WeaponCategory.BotanistOff => FullEquipType.GardenScythe,
|
||||||
|
WeaponCategory.FisherMain => FullEquipType.FishingRod,
|
||||||
|
WeaponCategory.Rogue => FullEquipType.Gig,
|
||||||
|
WeaponCategory.DarkKnight => FullEquipType.Broadsword,
|
||||||
|
WeaponCategory.Machinist => FullEquipType.Gun,
|
||||||
|
WeaponCategory.Astrologian => FullEquipType.Orrery,
|
||||||
|
WeaponCategory.Samurai => FullEquipType.Katana,
|
||||||
|
WeaponCategory.RedMage => FullEquipType.Rapier,
|
||||||
|
WeaponCategory.Scholar => FullEquipType.Book,
|
||||||
|
WeaponCategory.FisherOff => FullEquipType.Gig,
|
||||||
|
WeaponCategory.BlueMage => FullEquipType.Cane,
|
||||||
|
WeaponCategory.Gunbreaker => FullEquipType.Gunblade,
|
||||||
|
WeaponCategory.Dancer => FullEquipType.Glaives,
|
||||||
|
WeaponCategory.Reaper => FullEquipType.Scythe,
|
||||||
|
WeaponCategory.Sage => FullEquipType.Nouliths,
|
||||||
|
_ => FullEquipType.Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static FullEquipType Offhand(this FullEquipType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
FullEquipType.Fists => FullEquipType.Fists,
|
||||||
|
FullEquipType.Sword => FullEquipType.Shield,
|
||||||
|
FullEquipType.Wand => FullEquipType.Shield,
|
||||||
|
FullEquipType.Daggers => FullEquipType.Daggers,
|
||||||
|
FullEquipType.Gun => FullEquipType.Gun,
|
||||||
|
FullEquipType.Orrery => FullEquipType.Orrery,
|
||||||
|
FullEquipType.Rapier => FullEquipType.Rapier,
|
||||||
|
FullEquipType.Glaives => FullEquipType.Glaives,
|
||||||
|
_ => FullEquipType.Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly IReadOnlyList<FullEquipType> WeaponTypes
|
||||||
|
= Enum.GetValues<FullEquipType>().Where(v => v.IsWeapon()).ToArray();
|
||||||
|
|
||||||
|
public static readonly IReadOnlyList<FullEquipType> ToolTypes
|
||||||
|
= Enum.GetValues<FullEquipType>().Where(v => v.IsTool()).ToArray();
|
||||||
|
|
||||||
|
public static readonly IReadOnlyList<FullEquipType> EquipmentTypes
|
||||||
|
= Enum.GetValues<FullEquipType>().Where(v => v.IsEquipment()).ToArray();
|
||||||
|
|
||||||
|
public static readonly IReadOnlyList<FullEquipType> AccessoryTypes
|
||||||
|
= Enum.GetValues<FullEquipType>().Where(v => v.IsAccessory()).ToArray();
|
||||||
|
}
|
||||||
|
|
@ -51,159 +51,3 @@ public enum WeaponCategory : byte
|
||||||
Reaper = 108,
|
Reaper = 108,
|
||||||
Sage = 109,
|
Sage = 109,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class WeaponCategoryExtensions
|
|
||||||
{
|
|
||||||
public static WeaponCategory AllowsOffHand( this WeaponCategory category )
|
|
||||||
=> category switch
|
|
||||||
{
|
|
||||||
WeaponCategory.Pugilist => WeaponCategory.Pugilist,
|
|
||||||
WeaponCategory.Gladiator => WeaponCategory.Shield,
|
|
||||||
WeaponCategory.Marauder => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Archer => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Lancer => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Thaumaturge1 => WeaponCategory.Shield,
|
|
||||||
WeaponCategory.Thaumaturge2 => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Conjurer1 => WeaponCategory.Shield,
|
|
||||||
WeaponCategory.Conjurer2 => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Arcanist => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Shield => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.CarpenterMain => WeaponCategory.CarpenterOff,
|
|
||||||
WeaponCategory.CarpenterOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.BlacksmithMain => WeaponCategory.BlacksmithOff,
|
|
||||||
WeaponCategory.BlacksmithOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.ArmorerMain => WeaponCategory.ArmorerOff,
|
|
||||||
WeaponCategory.ArmorerOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.GoldsmithMain => WeaponCategory.GoldsmithOff,
|
|
||||||
WeaponCategory.GoldsmithOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.LeatherworkerMain => WeaponCategory.LeatherworkerOff,
|
|
||||||
WeaponCategory.LeatherworkerOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.WeaverMain => WeaponCategory.WeaverOff,
|
|
||||||
WeaponCategory.WeaverOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.AlchemistMain => WeaponCategory.AlchemistOff,
|
|
||||||
WeaponCategory.AlchemistOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.CulinarianMain => WeaponCategory.CulinarianOff,
|
|
||||||
WeaponCategory.CulinarianOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.MinerMain => WeaponCategory.MinerOff,
|
|
||||||
WeaponCategory.MinerOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.BotanistMain => WeaponCategory.BotanistOff,
|
|
||||||
WeaponCategory.BotanistOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.FisherMain => WeaponCategory.FisherOff,
|
|
||||||
WeaponCategory.Rogue => WeaponCategory.Rogue,
|
|
||||||
WeaponCategory.DarkKnight => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Machinist => WeaponCategory.Machinist,
|
|
||||||
WeaponCategory.Astrologian => WeaponCategory.Astrologian,
|
|
||||||
WeaponCategory.Samurai => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.RedMage => WeaponCategory.RedMage,
|
|
||||||
WeaponCategory.Scholar => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.FisherOff => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.BlueMage => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Gunbreaker => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Dancer => WeaponCategory.Dancer,
|
|
||||||
WeaponCategory.Reaper => WeaponCategory.Unknown,
|
|
||||||
WeaponCategory.Sage => WeaponCategory.Unknown,
|
|
||||||
_ => WeaponCategory.Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static EquipSlot ToSlot( this WeaponCategory category )
|
|
||||||
=> category switch
|
|
||||||
{
|
|
||||||
WeaponCategory.Pugilist => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Gladiator => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Marauder => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Archer => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Lancer => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Thaumaturge1 => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Thaumaturge2 => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Conjurer1 => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Conjurer2 => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Arcanist => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Shield => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.CarpenterMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.CarpenterOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.BlacksmithMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.BlacksmithOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.ArmorerMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.ArmorerOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.GoldsmithMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.GoldsmithOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.LeatherworkerMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.LeatherworkerOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.WeaverMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.WeaverOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.AlchemistMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.AlchemistOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.CulinarianMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.CulinarianOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.MinerMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.MinerOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.BotanistMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.BotanistOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.FisherMain => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Rogue => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.DarkKnight => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Machinist => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Astrologian => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Samurai => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.RedMage => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Scholar => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.FisherOff => EquipSlot.OffHand,
|
|
||||||
WeaponCategory.BlueMage => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Gunbreaker => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Dancer => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Reaper => EquipSlot.MainHand,
|
|
||||||
WeaponCategory.Sage => EquipSlot.MainHand,
|
|
||||||
_ => EquipSlot.Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static int ToIndex( this WeaponCategory category )
|
|
||||||
=> category switch
|
|
||||||
{
|
|
||||||
WeaponCategory.Pugilist => 0,
|
|
||||||
WeaponCategory.Gladiator => 1,
|
|
||||||
WeaponCategory.Marauder => 2,
|
|
||||||
WeaponCategory.Archer => 3,
|
|
||||||
WeaponCategory.Lancer => 4,
|
|
||||||
WeaponCategory.Thaumaturge1 => 5,
|
|
||||||
WeaponCategory.Thaumaturge2 => 6,
|
|
||||||
WeaponCategory.Conjurer1 => 7,
|
|
||||||
WeaponCategory.Conjurer2 => 8,
|
|
||||||
WeaponCategory.Arcanist => 9,
|
|
||||||
WeaponCategory.Shield => 10,
|
|
||||||
WeaponCategory.CarpenterMain => 11,
|
|
||||||
WeaponCategory.CarpenterOff => 12,
|
|
||||||
WeaponCategory.BlacksmithMain => 13,
|
|
||||||
WeaponCategory.BlacksmithOff => 14,
|
|
||||||
WeaponCategory.ArmorerMain => 15,
|
|
||||||
WeaponCategory.ArmorerOff => 16,
|
|
||||||
WeaponCategory.GoldsmithMain => 17,
|
|
||||||
WeaponCategory.GoldsmithOff => 18,
|
|
||||||
WeaponCategory.LeatherworkerMain => 19,
|
|
||||||
WeaponCategory.LeatherworkerOff => 20,
|
|
||||||
WeaponCategory.WeaverMain => 21,
|
|
||||||
WeaponCategory.WeaverOff => 22,
|
|
||||||
WeaponCategory.AlchemistMain => 23,
|
|
||||||
WeaponCategory.AlchemistOff => 24,
|
|
||||||
WeaponCategory.CulinarianMain => 25,
|
|
||||||
WeaponCategory.CulinarianOff => 26,
|
|
||||||
WeaponCategory.MinerMain => 27,
|
|
||||||
WeaponCategory.MinerOff => 28,
|
|
||||||
WeaponCategory.BotanistMain => 29,
|
|
||||||
WeaponCategory.BotanistOff => 30,
|
|
||||||
WeaponCategory.FisherMain => 31,
|
|
||||||
WeaponCategory.Rogue => 32,
|
|
||||||
WeaponCategory.DarkKnight => 33,
|
|
||||||
WeaponCategory.Machinist => 34,
|
|
||||||
WeaponCategory.Astrologian => 35,
|
|
||||||
WeaponCategory.Samurai => 36,
|
|
||||||
WeaponCategory.RedMage => 37,
|
|
||||||
WeaponCategory.Scholar => 38,
|
|
||||||
WeaponCategory.FisherOff => 39,
|
|
||||||
WeaponCategory.BlueMage => 40,
|
|
||||||
WeaponCategory.Gunbreaker => 41,
|
|
||||||
WeaponCategory.Dancer => 42,
|
|
||||||
WeaponCategory.Reaper => 43,
|
|
||||||
WeaponCategory.Sage => 44,
|
|
||||||
_ => -1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Lumina.Data.Parsing;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Penumbra.GameData.Data;
|
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Files;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
using Penumbra.Interop.Structs;
|
|
||||||
using Penumbra.Meta.Files;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods.ItemSwap;
|
|
||||||
|
|
||||||
public class EquipmentDataContainer
|
|
||||||
{
|
|
||||||
public Item Item;
|
|
||||||
public EquipSlot Slot;
|
|
||||||
public SetId ModelId;
|
|
||||||
public byte Variant;
|
|
||||||
|
|
||||||
public ImcManipulation ImcData;
|
|
||||||
|
|
||||||
public EqpManipulation EqpData;
|
|
||||||
public GmpManipulation GmpData;
|
|
||||||
|
|
||||||
// Example: Abyssos Helm / Body
|
|
||||||
public string AvfxPath = string.Empty;
|
|
||||||
|
|
||||||
// Example: Dodore Doublet, but unknown what it does?
|
|
||||||
public string SoundPath = string.Empty;
|
|
||||||
|
|
||||||
// Example: Crimson Standard Bracelet
|
|
||||||
public string DecalPath = string.Empty;
|
|
||||||
|
|
||||||
// Example: The Howling Spirit and The Wailing Spirit, but unknown what it does.
|
|
||||||
public string AnimationPath = string.Empty;
|
|
||||||
|
|
||||||
public Dictionary< GenderRace, GenderRaceContainer > Files = new();
|
|
||||||
|
|
||||||
public struct GenderRaceContainer
|
|
||||||
{
|
|
||||||
public EqdpManipulation Eqdp;
|
|
||||||
public GenderRace ModelRace;
|
|
||||||
public GenderRace MaterialRace;
|
|
||||||
public EstManipulation Est;
|
|
||||||
public string MdlPath;
|
|
||||||
public MtrlContainer[] MtrlPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct MtrlContainer
|
|
||||||
{
|
|
||||||
public string MtrlPath;
|
|
||||||
public string[] Textures;
|
|
||||||
public string Shader;
|
|
||||||
|
|
||||||
public MtrlContainer( string mtrlPath )
|
|
||||||
{
|
|
||||||
MtrlPath = mtrlPath;
|
|
||||||
var file = Dalamud.GameData.GetFile( mtrlPath );
|
|
||||||
if( file != null )
|
|
||||||
{
|
|
||||||
var mtrl = new MtrlFile( file.Data );
|
|
||||||
Textures = mtrl.Textures.Select( t => t.Path ).ToArray();
|
|
||||||
Shader = $"shader/sm5/shpk/{mtrl.ShaderPackage.Name}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Textures = Array.Empty< string >();
|
|
||||||
Shader = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static EstManipulation GetEstEntry( GenderRace genderRace, SetId setId, EquipSlot slot )
|
|
||||||
{
|
|
||||||
if( slot == EquipSlot.Head )
|
|
||||||
{
|
|
||||||
var entry = EstFile.GetDefault( EstManipulation.EstType.Head, genderRace, setId.Value );
|
|
||||||
return new EstManipulation( genderRace.Split().Item1, genderRace.Split().Item2, EstManipulation.EstType.Head, setId.Value, entry );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( slot == EquipSlot.Body )
|
|
||||||
{
|
|
||||||
var entry = EstFile.GetDefault( EstManipulation.EstType.Body, genderRace, setId.Value );
|
|
||||||
return new EstManipulation( genderRace.Split().Item1, genderRace.Split().Item2, EstManipulation.EstType.Body, setId.Value, entry );
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GenderRaceContainer GetGenderRace( GenderRace genderRace, SetId modelId, EquipSlot slot, ushort materialId )
|
|
||||||
{
|
|
||||||
var ret = new GenderRaceContainer()
|
|
||||||
{
|
|
||||||
Eqdp = GetEqdpEntry( genderRace, modelId, slot ),
|
|
||||||
Est = GetEstEntry( genderRace, modelId, slot ),
|
|
||||||
};
|
|
||||||
( ret.ModelRace, ret.MaterialRace ) = TraverseEqdpTree( genderRace, modelId, slot );
|
|
||||||
ret.MdlPath = GamePaths.Equipment.Mdl.Path( modelId, ret.ModelRace, slot );
|
|
||||||
ret.MtrlPaths = MtrlPaths( ret.MdlPath, ret.MaterialRace, modelId, materialId );
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EqdpManipulation GetEqdpEntry( GenderRace genderRace, SetId modelId, EquipSlot slot )
|
|
||||||
{
|
|
||||||
var entry = ExpandedEqdpFile.GetDefault( genderRace, slot.IsAccessory(), modelId.Value );
|
|
||||||
return new EqdpManipulation( entry, slot, genderRace.Split().Item1, genderRace.Split().Item2, modelId.Value );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MtrlContainer[] MtrlPaths( string mdlPath, GenderRace mtrlRace, SetId modelId, ushort materialId )
|
|
||||||
{
|
|
||||||
var file = Dalamud.GameData.GetFile( mdlPath );
|
|
||||||
if( file == null )
|
|
||||||
{
|
|
||||||
return Array.Empty< MtrlContainer >();
|
|
||||||
}
|
|
||||||
|
|
||||||
var mdl = new MdlFile( Dalamud.GameData.GetFile( mdlPath )!.Data );
|
|
||||||
var basePath = GamePaths.Equipment.Mtrl.FolderPath( modelId, ( byte )materialId );
|
|
||||||
var equipPart = $"e{modelId.Value:D4}";
|
|
||||||
var racePart = $"c{mtrlRace.ToRaceCode()}";
|
|
||||||
|
|
||||||
return mdl.Materials
|
|
||||||
.Where( m => m.Contains( equipPart ) )
|
|
||||||
.Select( m => new MtrlContainer( $"{basePath}{m.Replace( "c0101", racePart )}" ) )
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (GenderRace, GenderRace) TraverseEqdpTree( GenderRace genderRace, SetId modelId, EquipSlot slot )
|
|
||||||
{
|
|
||||||
var model = GenderRace.Unknown;
|
|
||||||
var material = GenderRace.Unknown;
|
|
||||||
var accessory = slot.IsAccessory();
|
|
||||||
foreach( var gr in genderRace.Dependencies() )
|
|
||||||
{
|
|
||||||
var entry = ExpandedEqdpFile.GetDefault( gr, accessory, modelId.Value );
|
|
||||||
var (b1, b2) = entry.ToBits( slot );
|
|
||||||
if( b1 && material == GenderRace.Unknown )
|
|
||||||
{
|
|
||||||
material = gr;
|
|
||||||
if( model != GenderRace.Unknown )
|
|
||||||
{
|
|
||||||
return ( model, material );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( b2 && model == GenderRace.Unknown )
|
|
||||||
{
|
|
||||||
model = gr;
|
|
||||||
if( material != GenderRace.Unknown )
|
|
||||||
{
|
|
||||||
return ( model, material );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ( GenderRace.MidlanderMale, GenderRace.MidlanderMale );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public EquipmentDataContainer( Item i )
|
|
||||||
{
|
|
||||||
Item = i;
|
|
||||||
LookupItem( i, out Slot, out ModelId, out Variant );
|
|
||||||
LookupImc( ModelId, Variant, Slot );
|
|
||||||
EqpData = new EqpManipulation( ExpandedEqpFile.GetDefault( ModelId.Value ), Slot, ModelId.Value );
|
|
||||||
GmpData = Slot == EquipSlot.Head ? new GmpManipulation( ExpandedGmpFile.GetDefault( ModelId.Value ), ModelId.Value ) : default;
|
|
||||||
|
|
||||||
|
|
||||||
foreach( var genderRace in Enum.GetValues< GenderRace >() )
|
|
||||||
{
|
|
||||||
if( CharacterUtility.EqdpIdx( genderRace, Slot.IsAccessory() ) < 0 )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Files[ genderRace ] = GetGenderRace( genderRace, ModelId, Slot, ImcData.Entry.MaterialId );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void LookupItem( Item i, out EquipSlot slot, out SetId modelId, out byte variant )
|
|
||||||
{
|
|
||||||
slot = ( ( EquipSlot )i.EquipSlotCategory.Row ).ToSlot();
|
|
||||||
if( !slot.IsEquipment() )
|
|
||||||
{
|
|
||||||
throw new ItemSwap.InvalidItemTypeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
modelId = ( ( Quad )i.ModelMain ).A;
|
|
||||||
variant = ( byte )( ( Quad )i.ModelMain ).B;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void LookupImc( SetId modelId, byte variant, EquipSlot slot )
|
|
||||||
{
|
|
||||||
var imc = ImcFile.GetDefault( GamePaths.Equipment.Imc.Path( modelId ), slot, variant, out var exists );
|
|
||||||
if( !exists )
|
|
||||||
{
|
|
||||||
throw new ItemSwap.InvalidImcException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImcData = new ImcManipulation( slot, variant, modelId.Value, imc );
|
|
||||||
if( imc.DecalId != 0 )
|
|
||||||
{
|
|
||||||
DecalPath = GamePaths.Equipment.Decal.Path( imc.DecalId );
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Figure out how this works.
|
|
||||||
if( imc.SoundId != 0 )
|
|
||||||
{
|
|
||||||
SoundPath = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( imc.VfxId != 0 )
|
|
||||||
{
|
|
||||||
AvfxPath = GamePaths.Equipment.Avfx.Path( modelId, imc.VfxId );
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Figure out how this works.
|
|
||||||
if( imc.MaterialAnimationId != 0 )
|
|
||||||
{
|
|
||||||
AnimationPath = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -252,9 +252,13 @@ public static class EquipmentSwap
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMC also controls sound, Example: Dodore Doublet, but unknown what it does?
|
||||||
|
// IMC also controls some material animation, Example: The Howling Spirit and The Wailing Spirit, but unknown what it does.
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Example: Crimson Standard Bracelet
|
||||||
public static bool AddDecal( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, byte decalId, MetaSwap imc )
|
public static bool AddDecal( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, byte decalId, MetaSwap imc )
|
||||||
{
|
{
|
||||||
if( decalId != 0 )
|
if( decalId != 0 )
|
||||||
|
|
@ -271,6 +275,8 @@ public static class EquipmentSwap
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Example: Abyssos Helm / Body
|
||||||
public static bool AddAvfx( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, SetId idFrom, SetId idTo, byte vfxId, MetaSwap imc )
|
public static bool AddAvfx( IReadOnlyDictionary< Utf8GamePath, FullPath > redirections, SetId idFrom, SetId idTo, byte vfxId, MetaSwap imc )
|
||||||
{
|
{
|
||||||
if( vfxId != 0 )
|
if( vfxId != 0 )
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,12 @@ public class ItemSwapContainer
|
||||||
NoSwaps,
|
NoSwaps,
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WriteMod( Mod mod, WriteType writeType = WriteType.NoSwaps )
|
public bool WriteMod( Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null, int groupIndex = -1, int optionIndex = 0 )
|
||||||
{
|
{
|
||||||
var convertedManips = new HashSet< MetaManipulation >( Swaps.Count );
|
var convertedManips = new HashSet< MetaManipulation >( Swaps.Count );
|
||||||
var convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
var convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
||||||
var convertedSwaps = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
var convertedSwaps = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
||||||
|
directory ??= mod.ModPath;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach( var swap in Swaps.SelectMany( s => s.WithChildren() ) )
|
foreach( var swap in Swaps.SelectMany( s => s.WithChildren() ) )
|
||||||
|
|
@ -62,7 +63,7 @@ public class ItemSwapContainer
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var path = file.GetNewPath( mod.ModPath.FullName );
|
var path = file.GetNewPath( directory.FullName );
|
||||||
var bytes = file.FileData.Write();
|
var bytes = file.FileData.Write();
|
||||||
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
|
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
|
||||||
File.WriteAllBytes( path, bytes );
|
File.WriteAllBytes( path, bytes );
|
||||||
|
|
@ -80,9 +81,9 @@ public class ItemSwapContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Penumbra.ModManager.OptionSetFiles( mod, -1, 0, convertedFiles );
|
Penumbra.ModManager.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
|
||||||
Penumbra.ModManager.OptionSetFileSwaps( mod, -1, 0, convertedSwaps );
|
Penumbra.ModManager.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
|
||||||
Penumbra.ModManager.OptionSetManipulations( mod, -1, 0, convertedManips );
|
Penumbra.ModManager.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
@ -120,7 +121,7 @@ public class ItemSwapContainer
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch
|
||||||
{
|
{
|
||||||
Swaps.Clear();
|
Swaps.Clear();
|
||||||
Loaded = false;
|
Loaded = false;
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,10 @@ public partial class Mod
|
||||||
return new DirectoryInfo( newModFolder );
|
return new DirectoryInfo( newModFolder );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the name for a group or option subfolder based on its parent folder and given name.
|
/// <summary>
|
||||||
// subFolderName should never be empty, and the result is unique and contains no invalid symbols.
|
/// Create the name for a group or option subfolder based on its parent folder and given name.
|
||||||
|
/// subFolderName should never be empty, and the result is unique and contains no invalid symbols.
|
||||||
|
/// </summary>
|
||||||
internal static DirectoryInfo? NewSubFolderName( DirectoryInfo parentFolder, string subFolderName )
|
internal static DirectoryInfo? NewSubFolderName( DirectoryInfo parentFolder, string subFolderName )
|
||||||
{
|
{
|
||||||
var newModFolderBase = NewOptionDirectory( parentFolder, subFolderName );
|
var newModFolderBase = NewOptionDirectory( parentFolder, subFolderName );
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,13 @@ using Penumbra.Util;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
using Penumbra.Interop.Resolver;
|
using Penumbra.Interop.Resolver;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||||
using ResidentResourceManager = Penumbra.Interop.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.ResidentResourceManager;
|
||||||
|
|
@ -63,6 +68,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
public static IObjectIdentifier Identifier { get; private set; } = null!;
|
public static IObjectIdentifier Identifier { get; private set; } = null!;
|
||||||
public static IGamePathParser GamePathParser { get; private set; } = null!;
|
public static IGamePathParser GamePathParser { get; private set; } = null!;
|
||||||
public static StainManager StainManager { get; private set; } = null!;
|
public static StainManager StainManager { get; private set; } = null!;
|
||||||
|
public static ItemData ItemData { get; private set; } = null!;
|
||||||
|
|
||||||
public static readonly List< Exception > ImcExceptions = new();
|
public static readonly List< Exception > ImcExceptions = new();
|
||||||
|
|
||||||
|
|
@ -92,6 +98,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData );
|
Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData );
|
||||||
GamePathParser = GameData.GameData.GetGamePathParser();
|
GamePathParser = GameData.GameData.GetGamePathParser();
|
||||||
StainManager = new StainManager( Dalamud.PluginInterface, Dalamud.GameData );
|
StainManager = new StainManager( Dalamud.PluginInterface, Dalamud.GameData );
|
||||||
|
ItemData = new ItemData( Dalamud.PluginInterface, Dalamud.GameData, Dalamud.GameData.Language );
|
||||||
Actors = new ActorManager( Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui, ResolveCutscene );
|
Actors = new ActorManager( Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui, ResolveCutscene );
|
||||||
|
|
||||||
Framework = new FrameworkManager();
|
Framework = new FrameworkManager();
|
||||||
|
|
@ -289,6 +296,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
Api?.Dispose();
|
Api?.Dispose();
|
||||||
_commandHandler?.Dispose();
|
_commandHandler?.Dispose();
|
||||||
StainManager?.Dispose();
|
StainManager?.Dispose();
|
||||||
|
ItemData?.Dispose();
|
||||||
Actors?.Dispose();
|
Actors?.Dispose();
|
||||||
Identifier?.Dispose();
|
Identifier?.Dispose();
|
||||||
Framework?.Dispose();
|
Framework?.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
|
@ -14,46 +17,48 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.ItemSwap;
|
using Penumbra.Mods.ItemSwap;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.UI.Classes;
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
public class ItemSwapWindow : IDisposable
|
public class ItemSwapWindow : IDisposable
|
||||||
{
|
{
|
||||||
private class EquipSelector : FilterComboCache< Item >
|
private enum SwapType
|
||||||
{
|
{
|
||||||
public EquipSelector()
|
Hat,
|
||||||
: base( Dalamud.GameData.GetExcelSheet< Item >()!.Where( i
|
Top,
|
||||||
=> ( ( EquipSlot )i.EquipSlotCategory.Row ).IsEquipment() && i.ModelMain != 0 && i.Name.RawData.Length > 0 ) )
|
Gloves,
|
||||||
|
Pants,
|
||||||
|
Shoes,
|
||||||
|
Earrings,
|
||||||
|
Necklace,
|
||||||
|
Bracelet,
|
||||||
|
Ring,
|
||||||
|
Hair,
|
||||||
|
Face,
|
||||||
|
Ears,
|
||||||
|
Tail,
|
||||||
|
Weapon,
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItemSelector : FilterComboCache< (string, Item) >
|
||||||
|
{
|
||||||
|
public ItemSelector( FullEquipType type )
|
||||||
|
: base( () => Penumbra.ItemData[ type ].Select( i => ( i.Name.ToDalamudString().TextValue, i ) ).ToArray() )
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
protected override string ToString( Item obj )
|
protected override string ToString( (string, Item) obj )
|
||||||
=> obj.Name.ToString();
|
=> obj.Item1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AccessorySelector : FilterComboCache< Item >
|
private class WeaponSelector : FilterComboCache< FullEquipType >
|
||||||
{
|
{
|
||||||
public AccessorySelector()
|
public WeaponSelector()
|
||||||
: base( Dalamud.GameData.GetExcelSheet< Item >()!.Where( i
|
: base( FullEquipTypeExtensions.WeaponTypes.Concat( FullEquipTypeExtensions.ToolTypes ) )
|
||||||
=> ( ( EquipSlot )i.EquipSlotCategory.Row ).IsAccessory() && i.ModelMain != 0 && i.Name.RawData.Length > 0 ) )
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
protected override string ToString( Item obj )
|
protected override string ToString( FullEquipType type )
|
||||||
=> obj.Name.ToString();
|
=> type.ToName();
|
||||||
}
|
|
||||||
|
|
||||||
private class SlotSelector : FilterComboCache< Item >
|
|
||||||
{
|
|
||||||
public readonly EquipSlot CurrentSlot;
|
|
||||||
|
|
||||||
public SlotSelector( EquipSlot slot )
|
|
||||||
: base( () => Dalamud.GameData.GetExcelSheet< Item >()!.Where( i
|
|
||||||
=> ( ( EquipSlot )i.EquipSlotCategory.Row ).ToSlot() == slot && i.ModelMain != 0 && i.Name.RawData.Length > 0 ).ToList() )
|
|
||||||
{
|
|
||||||
CurrentSlot = slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string ToString( Item obj )
|
|
||||||
=> obj.Name.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemSwapWindow()
|
public ItemSwapWindow()
|
||||||
|
|
@ -68,16 +73,29 @@ public class ItemSwapWindow : IDisposable
|
||||||
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly EquipSelector _equipSelector = new();
|
private readonly Dictionary< SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo) > _selectors = new()
|
||||||
private readonly AccessorySelector _accessorySelector = new();
|
{
|
||||||
private SlotSelector? _slotSelector;
|
[ SwapType.Hat ] = ( new ItemSelector( FullEquipType.Head ), new ItemSelector( FullEquipType.Head ), "Take this Hat", "and put it on this one" ),
|
||||||
|
[ SwapType.Top ] = ( new ItemSelector( FullEquipType.Body ), new ItemSelector( FullEquipType.Body ), "Take this Top", "and put it on this one" ),
|
||||||
|
[ SwapType.Gloves ] = ( new ItemSelector( FullEquipType.Hands ), new ItemSelector( FullEquipType.Hands ), "Take these Gloves", "and put them on these" ),
|
||||||
|
[ SwapType.Pants ] = ( new ItemSelector( FullEquipType.Legs ), new ItemSelector( FullEquipType.Legs ), "Take these Pants", "and put them on these" ),
|
||||||
|
[ SwapType.Shoes ] = ( new ItemSelector( FullEquipType.Feet ), new ItemSelector( FullEquipType.Feet ), "Take these Shoes", "and put them on these" ),
|
||||||
|
[ SwapType.Earrings ] = ( new ItemSelector( FullEquipType.Ears ), new ItemSelector( FullEquipType.Ears ), "Take these Earrings", "and put them on these" ),
|
||||||
|
[ SwapType.Necklace ] = ( new ItemSelector( FullEquipType.Neck ), new ItemSelector( FullEquipType.Neck ), "Take this Necklace", "and put it on this one" ),
|
||||||
|
[ SwapType.Bracelet ] = ( new ItemSelector( FullEquipType.Wrists ), new ItemSelector( FullEquipType.Wrists ), "Take these Bracelets", "and put them on these" ),
|
||||||
|
[ SwapType.Ring ] = ( new ItemSelector( FullEquipType.Finger ), new ItemSelector( FullEquipType.Finger ), "Take this Ring", "and put it on this one" ),
|
||||||
|
};
|
||||||
|
|
||||||
|
private ItemSelector? _weaponSource = null;
|
||||||
|
private ItemSelector? _weaponTarget = null;
|
||||||
|
private readonly WeaponSelector _slotSelector = new();
|
||||||
private readonly ItemSwapContainer _swapData = new();
|
private readonly ItemSwapContainer _swapData = new();
|
||||||
|
|
||||||
private Mod? _mod;
|
private Mod? _mod;
|
||||||
private ModSettings? _modSettings;
|
private ModSettings? _modSettings;
|
||||||
private bool _dirty;
|
private bool _dirty;
|
||||||
|
|
||||||
private SwapType _lastTab = SwapType.Equipment;
|
private SwapType _lastTab = SwapType.Hair;
|
||||||
private Gender _currentGender = Gender.Male;
|
private Gender _currentGender = Gender.Male;
|
||||||
private ModelRace _currentRace = ModelRace.Midlander;
|
private ModelRace _currentRace = ModelRace.Midlander;
|
||||||
private int _targetId = 0;
|
private int _targetId = 0;
|
||||||
|
|
@ -87,11 +105,12 @@ public class ItemSwapWindow : IDisposable
|
||||||
private string _newModName = string.Empty;
|
private string _newModName = string.Empty;
|
||||||
private string _newGroupName = "Swaps";
|
private string _newGroupName = "Swaps";
|
||||||
private string _newOptionName = string.Empty;
|
private string _newOptionName = string.Empty;
|
||||||
|
private IModGroup? _selectedGroup = null;
|
||||||
|
private bool _subModValid = false;
|
||||||
private bool _useFileSwaps = true;
|
private bool _useFileSwaps = true;
|
||||||
|
|
||||||
private Item[]? _affectedItems;
|
private Item[]? _affectedItems;
|
||||||
|
|
||||||
|
|
||||||
public void UpdateMod( Mod mod, ModSettings? settings )
|
public void UpdateMod( Mod mod, ModSettings? settings )
|
||||||
{
|
{
|
||||||
if( mod == _mod && settings == _modSettings )
|
if( mod == _mod && settings == _modSettings )
|
||||||
|
|
@ -108,6 +127,7 @@ public class ItemSwapWindow : IDisposable
|
||||||
_mod = mod;
|
_mod = mod;
|
||||||
_modSettings = settings;
|
_modSettings = settings;
|
||||||
_swapData.LoadMod( _mod, _modSettings );
|
_swapData.LoadMod( _mod, _modSettings );
|
||||||
|
UpdateOption();
|
||||||
_dirty = true;
|
_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,15 +140,26 @@ public class ItemSwapWindow : IDisposable
|
||||||
|
|
||||||
_swapData.Clear();
|
_swapData.Clear();
|
||||||
_loadException = null;
|
_loadException = null;
|
||||||
|
_affectedItems = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch( _lastTab )
|
switch( _lastTab )
|
||||||
{
|
{
|
||||||
case SwapType.Equipment when _slotSelector?.CurrentSelection != null && _equipSelector.CurrentSelection != null:
|
case SwapType.Hat:
|
||||||
_affectedItems = _swapData.LoadEquipment( _equipSelector.CurrentSelection, _slotSelector.CurrentSelection );
|
case SwapType.Top:
|
||||||
break;
|
case SwapType.Gloves:
|
||||||
case SwapType.Accessory when _slotSelector?.CurrentSelection != null && _accessorySelector.CurrentSelection != null:
|
case SwapType.Pants:
|
||||||
_affectedItems = _swapData.LoadEquipment( _accessorySelector.CurrentSelection, _slotSelector.CurrentSelection );
|
case SwapType.Shoes:
|
||||||
|
case SwapType.Earrings:
|
||||||
|
case SwapType.Necklace:
|
||||||
|
case SwapType.Bracelet:
|
||||||
|
case SwapType.Ring:
|
||||||
|
var values = _selectors[ _lastTab ];
|
||||||
|
if( values.Source.CurrentSelection.Item2 != null && values.Target.CurrentSelection.Item2 != null )
|
||||||
|
{
|
||||||
|
_affectedItems = _swapData.LoadEquipment( values.Target.CurrentSelection.Item2, values.Source.CurrentSelection.Item2 );
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization( BodySlot.Hair, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId );
|
_swapData.LoadCustomization( BodySlot.Hair, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId );
|
||||||
|
|
@ -143,8 +174,6 @@ public class ItemSwapWindow : IDisposable
|
||||||
_swapData.LoadCustomization( BodySlot.Tail, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId );
|
_swapData.LoadCustomization( BodySlot.Tail, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId );
|
||||||
break;
|
break;
|
||||||
case SwapType.Weapon: break;
|
case SwapType.Weapon: break;
|
||||||
case SwapType.Minion: break;
|
|
||||||
case SwapType.Mount: break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
@ -169,17 +198,13 @@ public class ItemSwapWindow : IDisposable
|
||||||
private string CreateDescription()
|
private string CreateDescription()
|
||||||
=> $"Created by swapping {_lastTab} {_sourceId} onto {_lastTab} {_targetId} for {_currentRace.ToName()} {_currentGender.ToName()}s in {_mod!.Name}.";
|
=> $"Created by swapping {_lastTab} {_sourceId} onto {_lastTab} {_targetId} for {_currentRace.ToName()} {_currentGender.ToName()}s in {_mod!.Name}.";
|
||||||
|
|
||||||
private void DrawHeaderLine( float width )
|
private void UpdateOption()
|
||||||
{
|
{
|
||||||
var newModAvailable = _loadException == null && _swapData.Loaded;
|
_selectedGroup = _mod?.Groups.FirstOrDefault( g => g.Name == _newGroupName );
|
||||||
|
_subModValid = _mod != null && _newGroupName.Length > 0 && _newOptionName.Length > 0 && ( _selectedGroup?.All( o => o.Name != _newOptionName ) ?? true );
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( width );
|
private void CreateMod()
|
||||||
if( ImGui.InputTextWithHint( "##newModName", "New Mod Name...", ref _newModName, 64 ) )
|
|
||||||
{ }
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
var tt = "Create a new mod of the given name containing only the swap.";
|
|
||||||
if( ImGuiUtil.DrawDisabledButton( "Create New Mod", new Vector2( width / 2, 0 ), tt, !newModAvailable || _newModName.Length == 0 ) )
|
|
||||||
{
|
{
|
||||||
var newDir = Mod.CreateModFolder( Penumbra.ModManager.BasePath, _newModName );
|
var newDir = Mod.CreateModFolder( Penumbra.ModManager.BasePath, _newModName );
|
||||||
Mod.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty );
|
Mod.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty );
|
||||||
|
|
@ -191,21 +216,118 @@ public class ItemSwapWindow : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CreateOption()
|
||||||
|
{
|
||||||
|
if( _mod == null || !_subModValid )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupCreated = false;
|
||||||
|
var dirCreated = false;
|
||||||
|
var optionCreated = false;
|
||||||
|
DirectoryInfo? optionFolderName = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
optionFolderName = Mod.NewSubFolderName( new DirectoryInfo( Path.Combine( _mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName ) ), _newOptionName );
|
||||||
|
if( optionFolderName?.Exists == true )
|
||||||
|
{
|
||||||
|
throw new Exception( $"The folder {optionFolderName.FullName} for the option already exists." );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( optionFolderName != null )
|
||||||
|
{
|
||||||
|
if( _selectedGroup == null )
|
||||||
|
{
|
||||||
|
Penumbra.ModManager.AddModGroup( _mod, GroupType.Multi, _newGroupName );
|
||||||
|
_selectedGroup = _mod.Groups.Last();
|
||||||
|
groupCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Penumbra.ModManager.AddOption( _mod, _mod.Groups.IndexOf( _selectedGroup ), _newOptionName );
|
||||||
|
optionCreated = true;
|
||||||
|
optionFolderName = Directory.CreateDirectory( optionFolderName.FullName );
|
||||||
|
dirCreated = true;
|
||||||
|
if( !_swapData.WriteMod( _mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps, optionFolderName,
|
||||||
|
_mod.Groups.IndexOf( _selectedGroup ), _selectedGroup.Count - 1 ) )
|
||||||
|
{
|
||||||
|
throw new Exception( "Failure writing files for mod swap." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
ChatUtil.NotificationMessage( $"Could not create new Swap Option:\n{e}", "Error", NotificationType.Error );
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( optionCreated && _selectedGroup != null )
|
||||||
|
{
|
||||||
|
Penumbra.ModManager.DeleteOption( _mod, _mod.Groups.IndexOf( _selectedGroup ), _selectedGroup.Count - 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( groupCreated )
|
||||||
|
{
|
||||||
|
Penumbra.ModManager.DeleteModGroup( _mod, _mod.Groups.IndexOf( _selectedGroup! ) );
|
||||||
|
_selectedGroup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( dirCreated && optionFolderName != null )
|
||||||
|
{
|
||||||
|
Directory.Delete( optionFolderName.FullName, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeaderLine( float width )
|
||||||
|
{
|
||||||
|
var newModAvailable = _loadException == null && _swapData.Loaded;
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth( width );
|
||||||
|
if( ImGui.InputTextWithHint( "##newModName", "New Mod Name...", ref _newModName, 64 ) )
|
||||||
|
{ }
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var tt = !newModAvailable
|
||||||
|
? "No swap is currently loaded."
|
||||||
|
: _newModName.Length == 0
|
||||||
|
? "Please enter a name for your mod."
|
||||||
|
: "Create a new mod of the given name containing only the swap.";
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( "Create New Mod", new Vector2( width / 2, 0 ), tt, !newModAvailable || _newModName.Length == 0 ) )
|
||||||
|
{
|
||||||
|
CreateMod();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui.SetNextItemWidth( ( width - ImGui.GetStyle().ItemSpacing.X ) / 2 );
|
ImGui.SetNextItemWidth( ( width - ImGui.GetStyle().ItemSpacing.X ) / 2 );
|
||||||
if( ImGui.InputTextWithHint( "##groupName", "Group Name...", ref _newGroupName, 32 ) )
|
if( ImGui.InputTextWithHint( "##groupName", "Group Name...", ref _newGroupName, 32 ) )
|
||||||
{ }
|
{
|
||||||
|
UpdateOption();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth( ( width - ImGui.GetStyle().ItemSpacing.X ) / 2 );
|
ImGui.SetNextItemWidth( ( width - ImGui.GetStyle().ItemSpacing.X ) / 2 );
|
||||||
if( ImGui.InputTextWithHint( "##optionName", "New Option Name...", ref _newOptionName, 32 ) )
|
if( ImGui.InputTextWithHint( "##optionName", "New Option Name...", ref _newOptionName, 32 ) )
|
||||||
{ }
|
{
|
||||||
|
UpdateOption();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
tt = "Create a new option inside this mod containing only the swap.";
|
tt = !_subModValid
|
||||||
if( ImGuiUtil.DrawDisabledButton( "Create New Option (WIP)", new Vector2( width / 2, 0 ), tt,
|
? "An option with that name already exists in that group, or no name is specified."
|
||||||
true || !newModAvailable || _newGroupName.Length == 0 || _newOptionName.Length == 0 || _mod == null || _mod.AllSubMods.Any( m => m.Name == _newOptionName ) ) )
|
: !newModAvailable
|
||||||
{ }
|
? "Create a new option inside this mod containing only the swap."
|
||||||
|
: "Create a new option (and possibly Multi-Group) inside the currently selected mod containing the swap.";
|
||||||
|
if( ImGuiUtil.DrawDisabledButton( "Create New Option", new Vector2( width / 2, 0 ), tt, !newModAvailable || !_subModValid ) )
|
||||||
|
{
|
||||||
|
CreateOption();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var newPos = new Vector2( ImGui.GetCursorPosX() + 10 * ImGuiHelpers.GlobalScale, ImGui.GetCursorPosY() - ( ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y ) / 2 );
|
var newPos = new Vector2( ImGui.GetCursorPosX() + 10 * ImGuiHelpers.GlobalScale, ImGui.GetCursorPosY() - ( ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y ) / 2 );
|
||||||
|
|
@ -214,25 +336,19 @@ public class ItemSwapWindow : IDisposable
|
||||||
ImGuiUtil.HoverTooltip( "Use File Swaps." );
|
ImGuiUtil.HoverTooltip( "Use File Swaps." );
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SwapType
|
|
||||||
{
|
|
||||||
Equipment,
|
|
||||||
Accessory,
|
|
||||||
Hair,
|
|
||||||
Face,
|
|
||||||
Ears,
|
|
||||||
Tail,
|
|
||||||
Weapon,
|
|
||||||
Minion,
|
|
||||||
Mount,
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawSwapBar()
|
private void DrawSwapBar()
|
||||||
{
|
{
|
||||||
using var bar = ImRaii.TabBar( "##swapBar", ImGuiTabBarFlags.None );
|
using var bar = ImRaii.TabBar( "##swapBar", ImGuiTabBarFlags.None );
|
||||||
|
|
||||||
DrawArmorSwap();
|
DrawEquipmentSwap( SwapType.Hat );
|
||||||
DrawAccessorySwap();
|
DrawEquipmentSwap( SwapType.Top );
|
||||||
|
DrawEquipmentSwap( SwapType.Gloves );
|
||||||
|
DrawEquipmentSwap( SwapType.Pants );
|
||||||
|
DrawEquipmentSwap( SwapType.Shoes );
|
||||||
|
DrawEquipmentSwap( SwapType.Earrings );
|
||||||
|
DrawEquipmentSwap( SwapType.Necklace );
|
||||||
|
DrawEquipmentSwap( SwapType.Bracelet );
|
||||||
|
DrawEquipmentSwap( SwapType.Ring );
|
||||||
DrawHairSwap();
|
DrawHairSwap();
|
||||||
DrawFaceSwap();
|
DrawFaceSwap();
|
||||||
DrawEarSwap();
|
DrawEarSwap();
|
||||||
|
|
@ -254,62 +370,37 @@ public class ItemSwapWindow : IDisposable
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawArmorSwap()
|
private void DrawEquipmentSwap( SwapType type )
|
||||||
{
|
{
|
||||||
using var tab = DrawTab( SwapType.Equipment );
|
using var tab = DrawTab( type );
|
||||||
if( !tab )
|
if( !tab )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (sourceSelector, targetSelector, text1, text2) = _selectors[ type ];
|
||||||
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
|
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted( "Take this piece of equipment" );
|
ImGui.TextUnformatted( text1 );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if( _equipSelector.Draw( "##itemTarget", _equipSelector.CurrentSelection?.Name.ToString() ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() ) )
|
_dirty |= sourceSelector.Draw( "##itemSource", sourceSelector.CurrentSelection.Item1 ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
||||||
{
|
|
||||||
var slot = ( ( EquipSlot )( _equipSelector.CurrentSelection?.EquipSlotCategory.Row ?? 0 ) ).ToSlot();
|
|
||||||
if( slot != _slotSelector?.CurrentSlot )
|
|
||||||
_slotSelector = new SlotSelector( slot );
|
|
||||||
_dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted( "And put it on this one" );
|
ImGui.TextUnformatted( text2 );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_slotSelector ??= new SlotSelector( EquipSlot.Unknown );
|
_dirty |= targetSelector.Draw( "##itemTarget", targetSelector.CurrentSelection.Item1 ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
||||||
_dirty |= _slotSelector.Draw( "##itemSource", _slotSelector.CurrentSelection?.Name.ToString() ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawAccessorySwap()
|
if( _affectedItems is { Length: > 0 } )
|
||||||
{
|
{
|
||||||
using var tab = DrawTab( SwapType.Accessory );
|
ImGui.SameLine();
|
||||||
if( !tab )
|
ImGuiUtil.DrawTextButton( $"which will also affect {_affectedItems.Length} other Items.", Vector2.Zero, Colors.PressEnterWarningBg );
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
{
|
{
|
||||||
return;
|
ImGui.SetTooltip( string.Join( '\n', _affectedItems.Select( i => i.Name.ToDalamudString().TextValue ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.AlignTextToFramePadding();
|
|
||||||
ImGui.TextUnformatted( "Take this accessory" );
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
if( _accessorySelector.Draw( "##itemTarget", _accessorySelector.CurrentSelection?.Name.ToString() ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() ) )
|
|
||||||
{
|
|
||||||
var slot = ( ( EquipSlot )( _accessorySelector.CurrentSelection?.EquipSlotCategory.Row ?? 0 ) ).ToSlot();
|
|
||||||
if( slot != _slotSelector?.CurrentSlot )
|
|
||||||
_slotSelector = new SlotSelector( slot );
|
|
||||||
_dirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.AlignTextToFramePadding();
|
|
||||||
ImGui.TextUnformatted( "And put it on this one" );
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
_slotSelector ??= new SlotSelector( EquipSlot.Unknown );
|
|
||||||
_dirty |= _slotSelector.Draw( "##itemSource", _slotSelector.CurrentSelection?.Name.ToString() ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawHairSwap()
|
private void DrawHairSwap()
|
||||||
|
|
@ -379,6 +470,36 @@ public class ItemSwapWindow : IDisposable
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted( "Select the weapon or tool you want" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if( _slotSelector.Draw( "##weaponSlot", _slotSelector.CurrentSelection.ToName(), InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() ) )
|
||||||
|
{
|
||||||
|
_dirty = true;
|
||||||
|
_weaponSource = new ItemSelector( _slotSelector.CurrentSelection );
|
||||||
|
_weaponTarget = new ItemSelector( _slotSelector.CurrentSelection );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_dirty = _weaponSource == null || _weaponTarget == null;
|
||||||
|
_weaponSource ??= new ItemSelector( _slotSelector.CurrentSelection );
|
||||||
|
_weaponTarget ??= new ItemSelector( _slotSelector.CurrentSelection );
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted( "and put this variant of it" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
_dirty |= _weaponSource.Draw( "##weaponSource", _weaponSource.CurrentSelection.Item1 ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted( "onto this one" );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
_dirty |= _weaponTarget.Draw( "##weaponTarget", _weaponTarget.CurrentSelection.Item1 ?? string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
|
||||||
}
|
}
|
||||||
|
|
||||||
private const float InputWidth = 120;
|
private const float InputWidth = 120;
|
||||||
|
|
@ -444,15 +565,20 @@ public class ItemSwapWindow : IDisposable
|
||||||
private string NonExistentText()
|
private string NonExistentText()
|
||||||
=> _lastTab switch
|
=> _lastTab switch
|
||||||
{
|
{
|
||||||
SwapType.Equipment => "One of the selected pieces of equipment does not seem to exist.",
|
SwapType.Hat => "One of the selected hats does not seem to exist.",
|
||||||
SwapType.Accessory => "One of the selected accessories does not seem to exist.",
|
SwapType.Top => "One of the selected tops does not seem to exist.",
|
||||||
|
SwapType.Gloves => "One of the selected pairs of gloves does not seem to exist.",
|
||||||
|
SwapType.Pants => "One of the selected pants does not seem to exist.",
|
||||||
|
SwapType.Shoes => "One of the selected pairs of shoes does not seem to exist.",
|
||||||
|
SwapType.Earrings => "One of the selected earrings does not seem to exist.",
|
||||||
|
SwapType.Necklace => "One of the selected necklaces does not seem to exist.",
|
||||||
|
SwapType.Bracelet => "One of the selected bracelets does not seem to exist.",
|
||||||
|
SwapType.Ring => "One of the selected rings does not seem to exist.",
|
||||||
SwapType.Hair => "One of the selected hairstyles does not seem to exist for this gender and race combo.",
|
SwapType.Hair => "One of the selected hairstyles does not seem to exist for this gender and race combo.",
|
||||||
SwapType.Face => "One of the selected faces does not seem to exist for this gender and race combo.",
|
SwapType.Face => "One of the selected faces does not seem to exist for this gender and race combo.",
|
||||||
SwapType.Ears => "One of the selected ear types does not seem to exist for this gender and race combo.",
|
SwapType.Ears => "One of the selected ear types does not seem to exist for this gender and race combo.",
|
||||||
SwapType.Tail => "One of the selected tails does not seem to exist for this gender and race combo.",
|
SwapType.Tail => "One of the selected tails does not seem to exist for this gender and race combo.",
|
||||||
SwapType.Weapon => "One of the selected weapons does not seem to exist.",
|
SwapType.Weapon => "One of the selected weapons or tools does not seem to exist.",
|
||||||
SwapType.Minion => "One of the selected minions does not seem to exist.",
|
|
||||||
SwapType.Mount => "One of the selected mounts does not seem to exist.",
|
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue